Skip to content

Instantly share code, notes, and snippets.

@maptastik
Created March 26, 2018 03:47
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save maptastik/7edc7ef3d5e065eb66a16f926940658f to your computer and use it in GitHub Desktop.
Save maptastik/7edc7ef3d5e065eb66a16f926940658f to your computer and use it in GitHub Desktop.
Esri-Leaflet Nearest Raleigh Park example
<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/leaflet@1.2.0/dist/leaflet.css" />
<script src="https://unpkg.com/leaflet@1.2.0"></script>
<!-- Load Esri Leaflet from CDN -->
<script src="https://unpkg.com/esri-leaflet@2.1.1"></script>
<!-- Leaflet Shape Markers -->
<script src="https://unpkg.com/leaflet-shape-markers@1.0.6"></script>
<!-- Esri Leaflet GP -->
<script src="https://unpkg.com/esri-leaflet-gp@2.0.3"></script>
<!-- Esri Leaflet Geocoder -->
<link rel="stylesheet" href="https://unpkg.com/esri-leaflet-geocoder@2.2.9/dist/esri-leaflet-geocoder.css">
<script src="https://unpkg.com/esri-leaflet-geocoder@2.2.8"></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>
/////////////////////////////////////////////////////////////////////
///// 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();
@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