Web Maps with Leaflet Presented at the Annual Conference of the Organization of Fish and Wildlife Information Managers September 30, 2014 • Flagstaff, Arizona Dyanne Fry Cortez • Texas Parks and Wildlife Department dyanne.cortez@tpwd.texas.gov Maps to Browse Texas State Parks - www.tpwd.texas.gov/state-parks/ Texas Lake Finder - www.tpwd.texas.gov/fishboat/fish/recreational/lakes/ Where ShareLunkers Come From www.tpwd.texas.gov/spdest/visitorcenters/tffc/sharelunker/lunkerlocations.phtml Demonstration map of Austin Public Libraries - www.sarasafavi.com/maps/libraries.html Loads GeoJSON layer made from shape file downloaded from a public data server Helpful Links Leaflet – leafletjs.com GeoJSON tutorial - leafletjs.com/examples/geojson.html GeoJSON specs - http://geojson.org/geojson-spec.html OpenStreetMaps tile usage http://wiki.openstreetmap.org/wiki/Tile_usage_policy and http://wiki.openstreetmap.org/wiki/Mapquest#MapQuest-hosted_map_tiles Sample Code from Demonstration Maps Lakes of the South Texas Plains www.tpwd.texas.gov/fishboat/fish/recreational/lakes/southtex_demo.phtml In HTML <head> section: <link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.5/leaflet.css" /> In <body>: <div id="map" style="width: 90%; height: 520px; border: 1px solid black;"> </div> <script src="http://cdn.leafletjs.com/leaflet-0.7.2/leaflet.js"></script> <script> var map = L.map('map').setView([28.0242,-98.769], 7); L.tileLayer('http://otile1.mqcdn.com/tiles/1.0.0/map/{z}/{x}/{y}.png', {attribution: 'Tiles Courtesy of <a href="http://www.mapquest.com/" target="_blank">MapQuest</a>, &copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors.'}).addTo(map); var lakeIcon = L.icon({ iconUrl: '/fishboat/fish/recreational/lakes/images/icons/leaflet_marker_grey.png', iconSize: [20, 32], // size of the icon iconAnchor: [10, 31], // point of the icon which will correspond to marker's location popupAnchor: [0, -35] // point from which the popup should open relative to the iconAnchor }); // add points L.marker([28.7789, -99.8281], {icon: lakeIcon, title: 'Averhoff'}).addTo(map) .bindPopup("<b>Averhoff Reservoir</b><br /><a href=\"/fishboat/fish/recreational/lakes/averhoff\">Fishing Information</a>"); L.marker([29.2425, -98.3752], {icon: lakeIcon, title: 'Braunig'}).addTo(map) .bindPopup("<b>Braunig Lake</b><br /><a href=\"/fishboat/fish/recreational/lakes/braunig\">Fishing Information</a>"); L.marker([29.2794, -98.3036], {icon: lakeIcon, title: 'Calaveras'}).addTo(map) .bindPopup("<b>Calaveras Lake</b><br /><a href=\"/fishboat/fish/recreational/lakes/calaveras\">Fishing Information</a>"); L.marker([27.534, -99.4469], {icon: lakeIcon, title: 'Casa Blanca'}).addTo(map) .bindPopup("<b>Casa Blanca International</b><br /><a href=\"/fishboat/fish/recreational/lakes/casa_blanca\">Fishing Information</a>"); L.marker([28.4825, -98.2435], {icon: lakeIcon, title: 'Choke Canyon'}).addTo(map) .bindPopup("<b>Choke Canyon Reservoir</b><br /><a href=\"/fishboat/fish/recreational/lakes/choke_canyon\">Fishing Information</a>"); L.marker([26.5579, -99.1598], {icon: lakeIcon, title: 'Falcon'}).addTo(map) .bindPopup("<b>Falcon Reservoir</b><br /><a href=\"/fishboat/fish/recreational/lakes/falcon\">Fishing Information"); L.marker([27.7907, -98.061], {icon: lakeIcon, title: 'Findley'}).addTo(map) .bindPopup("<b>Lake Findley</b><br /><a href=\"/fishboat/fish/recreational/lakes/findley\">Fishing Information</a>"); // Create overlay image var imageUrl = '/fishboat/fish/recreational/lakes/images/statemaps/stregion.gif', imageBounds = [[25.90123692658776, -100.7645521644355], [29.86978299705952, -97.13094330328251]]; L.imageOverlay(imageUrl, imageBounds, {opacity: 0.3}).addTo(map); </script> Community Fishing Lakes, South Texas Script to generate data file <?php [connect to database] // Function to convert odd characters function parseToJSON($htmlStr) { $myStr=str_replace('<','&lt;',$htmlStr); $myStr=str_replace('>','&gt;',$myStr); $myStr=str_replace('"','&quot;',$myStr); $myStr=str_replace("'",'&#39;',$myStr); $myStr=str_replace("&",'&amp;',$myStr); return $myStr; } // Select Community Fishing Lakes $query = "SELECT w.Lake_Code, c.County_Name, w.Water_Name, w.Area, w.Longitude, w.Latitude FROM waterbodies w, counties c WHERE w.County_Code = c.County_ID AND c.TRegion_ID = '5' AND w.CFL = 'Yes' AND w.Longitude IS NOT NULL AND w.Longitude != '0' ORDER BY County_Name, Water_Name"; $result = mysql_query($query); if (!$result) { die('Invalid query: ' . mysql_error()); } header("Content-type: text/javascript"); // Start file, echo parent node $myJSON = 'var cfls = {"type":"FeatureCollection","features":['; // Iterate through the rows, printing a line for each while ($row = @mysql_fetch_array($result)){ $myJSON .= '{"type":"Feature",'; $myJSON .= '"properties":'; $myJSON .= '{"ID":"'. $row[0] . '", '; $myJSON .= '"name":"' . parseToJSON($row[2]) . '", '; $myJSON .= '"county":"' . $row[1] . '",'; $myJSON .= '"size":"' . $row[3] . '"},'; $myJSON .= '"geometry":{"type":"Point","coordinates":[' . $row[4] . ',' . $row[5] . ']}'; $myJSON .= '},'; } // End file $myJSON .= ']}'; $myJSON = substr_replace($myJSON, "]}" ,-3); print $myJSON; ?> Run above script and save output to cfl5.js Here’s a snippet: var cfls = {"type":"FeatureCollection","features":[ {"type":"Feature","properties":{"ID":"1974", "name":"Brackenridge Park", "county":"Bexar","size":"10.00"}, "geometry":{"type":"Point","coordinates":[-98.4724,29.4617]}}, {"type":"Feature","properties":{"ID":"2602", "name":"Converse North Park City Lake", "county":"Bexar","size":"20.00"}, "geometry":{"type":"Point","coordinates":[-98.3129,29.5082]}}, {"type":"Feature","properties":{"ID":"1880", "name":"Earl Scott Pond", "county":"Bexar","size":"2.10"}, "geometry":{"type":"Point","coordinates":[-98.6289,29.5547]}}, {"type":"Feature","properties":{"ID":"0271", "name":"Elmendorf", "county":"Bexar","size":"30.00"}, "geometry":{"type":"Point","coordinates":[-98.5377,29.4252]}}, {"type":"Feature","properties":{"ID":"0232", "name":"Espada", "county":"Bexar","size":"19.00"}, "geometry":{"type":"Point","coordinates":[-98.4663,29.3463]}} ]} Page to display map www.tpwd.texas.gov/fishboat/fish/recreational/lakes/small_lakes_demo.phtml In HTML <head> section: <link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7.2/leaflet.css" /> In <body>: <script src="http://cdn.leafletjs.com/leaflet-0.7.2/leaflet.js"></script> // load data file <script type="text/javascript" src="/fishboat/fish/recreational/lakes/media/cfl5.js"></script> // start map code <script> var map = L.map('map').setView([28.0242,-98.769], 7); L.tileLayer('http://otile1.mqcdn.com/tiles/1.0.0/map/{z}/{x}/{y}.png', { attribution: 'Tiles Courtesy of <a href="http://www.mapquest.com/" target="_blank">MapQuest</a>, &copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors.' }).addTo(map); var imageUrl = '/fishboat/fish/recreational/lakes/images/statemaps/stregion.gif', imageBounds = [[25.90123692658776, -100.7645521644355], [29.86978299705952, 97.13094330328251]]; L.imageOverlay(imageUrl, imageBounds, {opacity: 0.3}).addTo(map); var lakeIcon = L.icon({ iconUrl: '/fishboat/fish/recreational/lakes/images/icons/0access_marker.png', iconSize: [10, 10], // size of the icon iconAnchor: [0, 0], // point of the icon which will correspond to marker's location popupAnchor: [5, 5], // point from which the popup should open }); function onEachFeature(feature, layer) { var popupContent = "<p><b>" + feature.properties.name + "</b><br>" + feature.properties.county + " County<br>" + feature.properties.size + " acres</p>"; layer.bindPopup(popupContent); } L.geoJson(cfls, { onEachFeature: onEachFeature, pointToLayer: function (feature, latlng) { return L.marker(latlng, {icon: lakeIcon, title: feature.properties.name}); } }).addTo(map); </script> ShareLunker Locations www.tpwd.state.tx.us/spdest/visitorcenters/tffc/sharelunker/lunkerlocations.phtml Data generator script $query = "SELECT lunker_fish.WB_code, waterbodies.Water_Name, waterbodies.Longitude, waterbodies.Latitude, COUNT(lunker_fish.ID) FROM lunker_fish, waterbodies WHERE lunker_fish.WB_code = waterbodies.Lake_Code AND waterbodies.Longitude IS NOT NULL AND waterbodies.Longitude != '0' GROUP BY waterbodies.Water_Name"; $result = mysql_query($query); if (!$result) { die('Invalid query: ' . mysql_error()); } // Start JSON statement $myJSON = 'var lunkerlakes = {"type":"FeatureCollection","features":['; // Iterate through the rows, printing a line for each while ($row = mysql_fetch_array($result)){ $myJSON .= '{"type":"Feature",'; $myJSON .= '"properties":'; $myJSON .= '{"ID":"'. $row[0] . '", '; $myJSON .= '"name":"' . parseToJSON($row[1]) . '", '; $myJSON .= '"fishcount":"' . $row[4] . '"},'; $myJSON .= '"geometry":{"type":"Point","coordinates":[' . $row[2] . ',' . $row[3] . ']}'; $myJSON .= '},'; } // End file $myJSON .= ']}'; $myJSON = substr_replace($myJSON, "]}" ,-3); print $myJSON; ?> Snippet of code var lunkerlakes = {"type":"FeatureCollection","features":[ {"type":"Feature","properties":{"ID":"0006", "name":"Alan Henry", "fishcount":"25"}, "geometry":{"type":"Point","coordinates":[-101.042,33.0602]}}, {"type":"Feature","properties":{"ID":"0014", "name":"Amistad", "fishcount":"12"}, "geometry":{"type":"Point","coordinates":[-101.057,29.4503]}}, {"type":"Feature","properties":{"ID":"0015", "name":"Amon G. Carter", "fishcount":"2"}, "geometry":{"type":"Point","coordinates":[-97.8576,33.4587]}}, {"type":"Feature","properties":{"ID":"0023", "name":"Arlington", "fishcount":"1"}, "geometry":{"type":"Point","coordinates":[-97.1994,32.7211]}}, {"type":"Feature","properties":{"ID":"0763", "name":"Waco", "fishcount":"1"}, "geometry":{"type":"Point","coordinates":[-97.1995,31.5811]}}, {"type":"Feature","properties":{"ID":"0781", "name":"White River Reservoir", "fishcount":"1"} ,"geometry":{"type":"Point","coordinates":[-101.084,33.4577]}} ]} Page to display map <div id="map" style="width: 98%; height: 700px; border: 1px solid black;"> </div> <script src="http://cdn.leafletjs.com/leaflet-0.7.2/leaflet.js"></script> <script type="text/javascript" src="/spdest/visitorcenters/tffc/sharelunker/include/sharelunkers.js"></script> <script> var map = L.map('map').setView([31,-100], 6); L.tileLayer('http://otile1.mqcdn.com/tiles/1.0.0/map/{z}/{x}/{y}.png', {attribution: 'Tiles Courtesy of <a href="http://www.mapquest.com/" target="_blank">MapQuest</a>, &copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors.' }).addTo(map); var lunkerIcon = L.icon({ iconUrl: '/fishboat/fish/recreational/lakes/images/icons/leaflet_marker.png', iconSize: [20, 32], // size of the icon iconAnchor: [10, 31], // point of the icon which will correspond to marker's location popupAnchor: [0, -35] // point from which the popup should open relative to the iconAnchor }); function onEachFeature(feature, layer) { // conditional statement for plural vs. singular fishcount if (feature.properties.fishcount > 1) { var popupContent = "<b>" + feature.properties.name + "</b><br>"+ feature.properties.fishcount + " ShareLunkers (<a href='archives/index.phtml?wcode=" + feature.properties.ID + "'>see list</a>)"; } else { var popupContent = "<b>" + feature.properties.name + "</b><br>" + feature.properties.fishcount + " ShareLunker (<a href='archives/index.phtml?wcode=" + feature.properties.ID + "'>details</a>)"; } layer.bindPopup(popupContent); } L.geoJson(lunkerlakes, { onEachFeature: onEachFeature, pointToLayer: function (feature, latlng) { return L.marker(latlng, {icon: lunkerIcon, title: feature.properties.name + " (" + feature.properties.fishcount + ")"}); } }).addTo(map); // Two river locations do not have lats and longs in the database and need to be added individually: L.marker([28.5442, -99.7891], {icon: lunkerIcon, title: 'Nueces River (1)'}).addTo(map) .bindPopup("<b>Nueces River</b><br />1 Sharelunker (<a href=\"archives/index.phtml?wcode=1422\">details</a>)"); L.marker([29.9335, -94.4827], {icon: lunkerIcon, title: 'Spindletop Bayou (1)'}).addTo(map) .bindPopup("<b>Spindletop Bayou</b><br />1 Sharelunker (<a href=\"archives/index.phtml?wcode=1563\">details</a>)"); </script>