Created
March 26, 2018 03:47
-
-
Save maptastik/7edc7ef3d5e065eb66a16f926940658f to your computer and use it in GitHub Desktop.
Esri-Leaflet Nearest Raleigh Park example
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<html> | |
<head> | |
<meta charset=utf-8 /> | |
<title>route to the closest facility</title> | |
<meta name='viewport' content='initial-scale=1,maximum-scale=1,user-scalable=no' /> | |
<!-- Load Leaflet from CDN--> | |
<link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/leaflet.css" /> | |
<script src="https://unpkg.com/[email protected]"></script> | |
<!-- Load Esri Leaflet from CDN --> | |
<script src="https://unpkg.com/[email protected]"></script> | |
<!-- Leaflet Shape Markers --> | |
<script src="https://unpkg.com/[email protected]"></script> | |
<!-- Esri Leaflet GP --> | |
<script src="https://unpkg.com/[email protected]"></script> | |
<!-- Esri Leaflet Geocoder --> | |
<link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/esri-leaflet-geocoder.css"> | |
<script src="https://unpkg.com/[email protected]"></script> | |
<!-- TurfJS --> | |
<script src='https://npmcdn.com/@turf/turf/turf.min.js'></script> | |
<!-- Pulsing Icon --> | |
<link rel="stylesheet" href="https://cdn.rawgit.com/mapshakers/leaflet-icon-pulse/add42abc/src/L.Icon.Pulse.css"> | |
<script src='https://cdn.rawgit.com/mapshakers/leaflet-icon-pulse/master/src/L.Icon.Pulse.js'></script> | |
<!-- Numeral.js for number formatting --> | |
<script src="//cdnjs.cloudflare.com/ajax/libs/numeral.js/2.0.6/numeral.min.js"></script> | |
<link rel="stylesheet" href="./style.css"> | |
</head> | |
<body> | |
<div id="map"></div> | |
<!-- <div id="info-box" class="leaflet-bar"> | |
<label> | |
click on the map to determine the best route to the nearest hospital. | |
</label> | |
</div> --> | |
<div id='info-box'> | |
<ul id='info-list'> | |
<li id='info-box-start'></li> | |
<li id='info-box-park'></li> | |
<li id='info-box-ndist' class='info-box-item'></li> | |
<li id='info-box-edist' class='info-box-item'></li> | |
<li id='info-box-ratio' class='info-box-item'></li> | |
<li id='info-box-hover'></li> | |
</ul> | |
</div> | |
<script src='./index.js'></script> | |
</body> | |
</html> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
///////////////////////////////////////////////////////////////////// | |
///// Functions to help out before creating and running main() ///// | |
///////////////////////////////////////////////////////////////////// | |
// Get park access points | |
function getParks() { | |
return new Promise(function(resolve, reject) { | |
let parks = L.esri.query({ | |
url: | |
"https://services7.arcgis.com/ocKjSej9kmTKCjNK/arcgis/rest/services/AccessPoints_LOS_20180301_FIRST/FeatureServer/0" | |
}); | |
parks.run(function(err, response, raw) { | |
if (err) return reject(err); | |
resolve(response); | |
}); | |
}); | |
} | |
// Format decimal values to return 2 decimal places | |
function twoDecimal(val) { | |
return numeral(val).format("0.00"); | |
} | |
////////////////////////// | |
///// MAIN FUNCTION ///// | |
//////////////////////// | |
async function main() { | |
// LAYERS | |
let routesLayer = L.featureGroup(); | |
let selectionLayer = L.featureGroup(); | |
let searchBuffer = L.featureGroup(); | |
let searchLayer = L.featureGroup(); | |
let pulsingIcon = L.icon.pulse({ | |
iconSize: [8, 8], | |
color: "#18FFFF", | |
fillColor: "#18FFFF", | |
animate: true, | |
heartbeat: 2 | |
}); | |
let endLayer = L.featureGroup(); | |
// Instantiate Map | |
let map = L.map("map").setView([35.77959, -78.638179], 13); | |
L.esri.basemapLayer("DarkGray").addTo(map); | |
// Add layers. These will start out empty | |
map.addLayer(routesLayer); | |
map.addLayer(selectionLayer); | |
map.addLayer(searchLayer); | |
map.addLayer(endLayer); | |
// Wait for and then retrieve parks data | |
var parks = await getParks(); | |
// Add parks to map | |
var parksLayer = L.geoJson(parks, { | |
pointToLayer: function(geojson, latlng) { | |
return L.circleMarker(latlng, { | |
color: "#fff", | |
weight: 0.25, | |
fillColor: "#18FFFF", | |
fillOpacity: 0.4, | |
radius: 4 | |
}); | |
} | |
}).addTo(map); | |
// use a service proxy to authenticate automagically | |
// https://developers.arcgis.com/authentication/working-with-proxies/ | |
let closestFacilityService = L.esri.GP.service({ | |
url: | |
"https://utility.arcgis.com/usrsvcs/appservices/FnVYYTap8ykTDC1m/rest/services/World/ClosestFacility/NAServer/ClosestFacility_World", | |
path: "solveClosestFacility" | |
}); | |
let closestFacilityTask = closestFacilityService.createTask(); | |
closestFacilityTask.setParam("returnCFRoutes", true); | |
// when someone clicks on the map, find the route to the nearest hospital | |
map.on("click", function(evt) { | |
// clear any existing | |
routesLayer.clearLayers(); | |
selectionLayer.clearLayers(); | |
searchBuffer.clearLayers(); | |
searchLayer.clearLayers(); | |
endLayer.clearLayers(); | |
var infoListItems = document | |
.getElementById("info-list") | |
.getElementsByTagName("li"); | |
console.log(infoListItems); | |
for (var i = 0; i < infoListItems.length - 1; i++) { | |
infoListItems[i].textContent = ""; | |
} | |
L.esri.Geocoding.reverseGeocode() | |
.latlng(evt.latlng) | |
.run(function(err, result, raw) { | |
console.log(raw); | |
document.getElementById("info-box-start").textContent = | |
"Start Location: " + raw.address.ShortLabel; | |
}); | |
// console.log(evt) | |
let lngLat = turf.point([evt.latlng.lng, evt.latlng.lat]); | |
searchBufferResult = turf.buffer(lngLat, 1.29, { units: "miles" }); | |
searchBuffer = L.geoJson(searchBufferResult, { | |
style: { | |
color: "#fff", | |
fillOpacity: 0, | |
lineCap: "mitre", | |
dashArray: "20, 10", | |
weight: 8 | |
} | |
}); | |
searchBuffer.addTo(map); | |
map.flyToBounds(searchBuffer.getBounds()); | |
let parksFeatures = parks.features; | |
let parksCollection = []; | |
for (i = 0; i < parksFeatures.length; i++) { | |
var coordinates = parksFeatures[i].geometry.coordinates; | |
parksCollection.push(coordinates); | |
} | |
var searchLayerResult = turf.pointsWithinPolygon( | |
turf.points(parksCollection), | |
turf.polygon(searchBufferResult.geometry.coordinates) | |
); | |
if (searchLayerResult.features.length > 0) { | |
searchLayer = L.geoJson(searchLayerResult, { | |
pointToLayer: function(geojson, latlng) { | |
return L.circleMarker(latlng, { | |
weight: 0, | |
fillOpacity: 0, | |
radius: 0 | |
}); | |
} | |
}); | |
searchLayer.addTo(map); | |
// add an selection icon to the map | |
selectionLayer.addLayer( | |
L.shapeMarkers.crossMarker(evt.latlng, 15, { | |
color: "white", | |
weight: 4 | |
}) | |
); | |
// supply the location of the map click as a service parameter | |
closestFacilityTask.setParam("incidents", evt.latlng); | |
closestFacilityTask.setParam("facilities", searchLayerResult); | |
// Get routes preferred for pedestrians | |
closestFacilityTask.setParam( | |
"restrictionAttributeNames", | |
"Preferred for Pedestrians" | |
); | |
// and call the solver | |
closestFacilityTask.run(function(err, res, raw) { | |
// draw the GeoJSON feature that was returned | |
console.log(err); | |
var routesFeature = res.routes.features[0]; | |
routesLayer.addLayer( | |
L.geoJSON(routesFeature, { | |
style: { | |
color: "#FFFF00", | |
weight: 2, | |
opacity: 1 | |
} | |
}) | |
); | |
var routesFeatureArray = routesFeature.features; | |
console.log(routesFeatureArray); | |
// Get length of route using TurfJS | |
var turfRouteLength = turf.length(routesFeature, { units: "miles" }); | |
// Get length of route using what was returned by closestFacilityTask() | |
var esriRouteLength = routesFeature.properties.Total_Miles; | |
document.getElementById("info-box-ndist").textContent = | |
"Route Distance: " + twoDecimal(esriRouteLength) + " mi"; | |
// Get location of end point in route | |
var end = | |
routesFeature.geometry.coordinates[ | |
routesFeature.geometry.coordinates.length - 1 | |
]; | |
var endBuffer = turf.buffer(turf.point(end), 0.05, { units: "miles" }); | |
var endPoint = turf.pointsWithinPolygon(parks, endBuffer); | |
document.getElementById("info-box-park").textContent = | |
"Park: " + endPoint.features[0].properties.PARK_NAME; | |
// Get Euclidean Distance from start point to end point | |
var euclidDist = turf.distance( | |
turf.point([evt.latlng.lng, evt.latlng.lat]), | |
turf.point(end), | |
{ units: "miles" } | |
); | |
document.getElementById("info-box-edist").textContent = | |
"Euclidean Distance: " + twoDecimal(euclidDist) + " mi"; | |
// Get ratio of route to euclidean distance | |
var distRatio = esriRouteLength / euclidDist; | |
document.getElementById("info-box-ratio").textContent = | |
"Circuity Ratio: " + twoDecimal(distRatio) + ":1"; | |
endLayer.addLayer( | |
L.marker(end.reverse(), { | |
icon: pulsingIcon | |
}) | |
); | |
}); | |
} else { | |
document.getElementById("info-box-park").innerHTML = | |
"<i>No parks in search area</i>"; | |
} | |
}); | |
} | |
main(); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@import url('https://fonts.googleapis.com/css?family=Oswald|Roboto'); | |
html, body { | |
margin: 0; | |
padding: 0; | |
font-family: 'Oswald', sans-serif; | |
} | |
#map { | |
position:absolute; | |
top: 0; | |
bottom: 0; | |
width: 100%; | |
} | |
#info-pane { | |
position: absolute; | |
top: 10px; | |
right: 10px; | |
z-index: 1000; | |
padding: 1em; | |
background: white; | |
max-width: 240px; | |
} | |
#info-box { | |
color: white; | |
position: absolute; | |
top: 0; | |
right: 0; | |
padding: 10px 10px 0 0; | |
max-width: 33%; | |
z-index: 1000; | |
} | |
#info-box h1 { | |
margin: 0; | |
font-size: 4em; | |
text-shadow: rgb(0, 0, 0) 0px 0px 20px, | |
rgb(0, 0, 0) 10px 10px 40px; | |
} | |
#info-box ul { | |
margin: 4px 0; | |
list-style-type: none; | |
font-size: 1.4em; | |
text-shadow: rgb(0, 0, 0) 0px 0px 6px, | |
rgb(0, 0, 0) 4px 4px 12px; | |
} | |
#info-box-hover { | |
font-style: italic; | |
} | |
@media (max-width: 900px) { | |
#info-box h1 { | |
font-size: 3em; | |
} | |
#info-box p { | |
font-size: 1.2em; | |
} | |
} | |
@media (max-width: 600px) { | |
#info-box h1 { | |
font-size: 2em; | |
} | |
#info-box p { | |
font-size: 1em; | |
} | |
} | |
@media (max-width: 400px) { | |
#info-box h1 { | |
font-size: 1.8em; | |
} | |
} | |
@media (max-width: 300px) { | |
#info-box h1 { | |
font-size: 1.2em; | |
} | |
#info-box p { | |
font-size: 0.8em; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment