A Pen by Raul Jimenez Ortega on CodePen.
Created
July 2, 2025 09:19
-
-
Save hhkaos/70faf59003bd2e3579e93b947620e7bb to your computer and use it in GitHub Desktop.
ArcGIS Developer Guide: Find the speed limit of the closest road
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
<!-- | |
To run this demo, you need to replace 'YOUR_ACCESS_TOKEN' with an access token from ArcGIS that has the correct privileges. | |
To get started, sign up for a free ArcGIS Location Platform account or a free trial of ArcGIS Online and create developer credentials. | |
https://developers.arcgis.com/documentation/security-and-authentication/get-started/ | |
--> | |
<html> | |
<head> | |
<meta charset="utf-8" /> | |
<meta | |
name="viewport" | |
content="initial-scale=1,maximum-scale=1,user-scalable=no" | |
/> | |
<script src="https://unpkg.com/[email protected]/dist/maplibre-gl.js"></script> | |
<link | |
href="https://unpkg.com/[email protected]/dist/maplibre-gl.css" | |
rel="stylesheet" | |
/> | |
<script | |
type="module" | |
src="https://js.arcgis.com/calcite-components/3.2.1/calcite.esm.js" | |
></script> | |
<script src="https://unpkg.com/@terraformer/arcgis"></script> | |
<script src="https://unpkg.com/@esri/arcgis-rest-request@4/dist/bundled/request.umd.js"></script> | |
<script src="https://unpkg.com/@esri/arcgis-rest-feature-service@4/dist/bundled/feature-service.umd.js"></script> | |
<style> | |
html, | |
body, | |
#map { | |
padding: 0; | |
margin: 0; | |
height: 100vh; | |
width: 100vw; | |
} | |
.button-container { | |
position: absolute; | |
right: 15px; | |
top: 15px; | |
z-index: 1001; | |
padding: 6px; | |
background-color: #ffffff; | |
} | |
</style> | |
</head> | |
<body> | |
<div class="button-container"> | |
<calcite-button id="snapButton" label="Run snap to roads operation" | |
>Snap GPS points</calcite-button | |
> | |
<calcite-button | |
id="resetButton" | |
icon-start="reset" | |
label="Remove result layers" | |
disabled | |
></calcite-button> | |
</div> | |
<div id="map"></div> | |
<script type="module"> | |
const appConfig = { | |
// for basemap styles service | |
ACCESS_TOKEN: "YOUR_ACCESS_TOKEN", | |
// Snap to roads operation URL | |
SNAP_TO_ROADS_URL: | |
"https://route-api.arcgis.com/arcgis/rest/services/World/SnapToRoadsSync/GPServer/SnapToRoads/execute", | |
// input points service URL | |
INPUT_TRACKS_URL: | |
"https://services3.arcgis.com/GVgbJbqm8hXASVYi/arcgis/rest/services/clt_track_points/FeatureServer/0", | |
// Basemap styles service URL | |
BASE_MAP_URL: | |
"https://basemapstyles-api.arcgis.com/arcgis/rest/services/styles/v2/styles/", | |
// Basemap family/style name | |
BASEMAP_STYLE_NAME: "arcgis/human-geography", | |
} | |
let inputPoints | |
// Create a blank featurecollection | |
const emptyDS = { | |
type: "FeatureCollection", | |
features: [], | |
} | |
// create a popup | |
const popup = new maplibregl.Popup() | |
// Instantiate a new map control | |
const map = new maplibregl.Map({ | |
container: "map", // the id of the div element | |
style: `${appConfig.BASE_MAP_URL}${appConfig.BASEMAP_STYLE_NAME}?token=${appConfig.ACCESS_TOKEN}`, // basemap style | |
zoom: 14, // starting zoom | |
center: [-80.8574, 35.2213], // starting location [longitude, latitude] | |
}) | |
/** | |
* | |
*/ | |
const addResults = async fromResponse => { | |
const snappedSource = map.getSource("snappedPoints") | |
await snappedSource.setData(fromResponse.snappedPoints) | |
const snappedLineSource = map.getSource("snappedLines") | |
await snappedLineSource.setData(fromResponse.snappedLines) | |
map.fitBounds(await snappedSource.getBounds()) | |
} | |
/** | |
* Run the snap to roads operation using our input track points and | |
* update the UI with it's progress. | |
*/ | |
const snapPoints = async () => { | |
const snapButton = document.getElementById("snapButton") | |
try { | |
snapButton.loading = true | |
map.getSource("snappedPoints").setData(emptyDS) | |
map.getSource("snappedLines").setData(emptyDS) | |
const headerParams = { | |
// Authorization: `Bearer ${appConfig.ACCESS_TOKEN}`, | |
"Content-Type": "application/x-www-form-urlencoded", | |
} | |
const agsPoints = Terraformer.geojsonToArcGIS(inputPoints) | |
const queryParams = new URLSearchParams({ | |
f: "json", | |
token: appConfig.ACCESS_TOKEN, | |
points: JSON.stringify({ features: agsPoints }), | |
return_location_fields: true, | |
return_lines: true, | |
road_properties_on_lines: JSON.stringify([ | |
"posted_speed_limit_mph", | |
"length_miles", | |
]), | |
}) | |
const service = appConfig.SNAP_TO_ROADS_URL | |
const response = await fetch(service, { | |
method: "POST", | |
headers: headerParams, | |
body: queryParams, | |
}) | |
const agsJSONResponse = await response.json() | |
// Convert ArcGIS JSON to GeoJSON | |
const geojsonResults = Terraformer.arcgisToGeoJSON( | |
agsJSONResponse.results[0].value | |
) | |
const lineResults = Terraformer.arcgisToGeoJSON( | |
agsJSONResponse.results[1].value | |
) | |
addResults({ | |
snappedPoints: geojsonResults, | |
snappedLines: lineResults, | |
}) | |
} catch (err) { | |
console.log(err) | |
} finally { | |
snapButton.loading = false | |
document.getElementById("resetButton").disabled = false | |
} | |
} | |
/** | |
* Map load event handler. Adds our layers to the map. | |
*/ | |
const mapDidLoad = async () => { | |
// Query the featureservice for our input tracks in GeoJSON format. | |
inputPoints = await arcgisRest.queryFeatures({ | |
url: appConfig.INPUT_TRACKS_URL, | |
where: "1=1", | |
f: "geojson", | |
returnGeometry: true, | |
}) | |
// Add track points to the map | |
map.addSource("inputPoints", { | |
type: "geojson", | |
data: inputPoints, | |
}) | |
map.addLayer({ | |
id: "inputPointsLayer", | |
type: "circle", | |
source: "inputPoints", | |
paint: { | |
"circle-color": "rgba(52, 122, 196, 0.78)", | |
"circle-stroke-width": 1, | |
"circle-radius": 5, | |
"circle-stroke-color": "rgba(255, 255, 255, 1)", | |
}, | |
}) | |
// add our result routes to the map | |
map.addSource("snappedLines", { | |
type: "geojson", | |
data: { | |
type: "FeatureCollection", | |
features: [], | |
}, | |
}) | |
map.addLayer({ | |
id: "snappedLinesLayer", | |
type: "line", | |
source: "snappedLines", | |
paint: { | |
"line-width": 6, | |
"line-color": [ | |
"interpolate", | |
["linear"], | |
["get", "posted_speed_limit_mph"], | |
20, | |
"#d53e4fff", | |
35, | |
"#fc8d59ff", | |
40, | |
"#fee08bff", | |
45, | |
"#e6f598ff", | |
50, | |
"#99d594ff", | |
55, | |
"#006837ff", | |
], | |
}, | |
}) | |
// add the snapped point results to the map | |
map.addSource("snappedPoints", { | |
type: "geojson", | |
data: { | |
type: "FeatureCollection", | |
features: [], | |
}, | |
}) | |
map.addLayer({ | |
id: "snappedPointsLayer", | |
type: "circle", | |
source: "snappedPoints", | |
paint: { | |
"circle-radius": 4, | |
"circle-color": "rgba(0, 0, 0, 0.75)", | |
"circle-stroke-width": 1, | |
"circle-stroke-color": "rgba(255, 255, 255, 0.75)", | |
}, | |
}) | |
// Listen for map mouse events to display popups | |
map.on("mouseenter", "snappedLinesLayer", e => { | |
map.getCanvas().style.cursor = "pointer" | |
popup | |
.setLngLat(e.lngLat) | |
.setHTML( | |
`Segment id: ${e.features[0].properties.ObjectID}<br> | |
Segment length (miles): ${e.features[0].properties.length_miles.toFixed( | |
4 | |
)}<br> | |
Speed limit (mph): ${e.features[0].properties.posted_speed_limit_mph.toFixed( | |
0 | |
)}` | |
) | |
.addTo(map) | |
}) | |
map.on("mouseleave", "snappedLinesLayer", () => { | |
map.getCanvas().style.cursor = "default" | |
popup.remove() | |
}) | |
} | |
// Add our layers to the map once it has been loaded | |
map.once("load", mapDidLoad) | |
// Add Esri attribution | |
// Learn more in https://esriurl.com/attribution | |
map._controls[0].options.customAttribution += " | Powered by Esri " | |
map._controls[0]._updateAttributions() | |
// Handle button click events | |
document | |
.getElementById("snapButton") | |
.addEventListener("click", snapPoints) | |
document.getElementById("resetButton").addEventListener("click", () => { | |
document.getElementById("resetButton").disabled = true | |
map.getSource("snappedPoints").setData(emptyDS) | |
map.getSource("snappedLines").setData(emptyDS) | |
}) | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment