Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save hhkaos/03f533d415f4b939970265a302a12166 to your computer and use it in GitHub Desktop.
Save hhkaos/03f533d415f4b939970265a302a12166 to your computer and use it in GitHub Desktop.
ArcGIS Developer Guide: Snap to roads
<!--
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"
/>
<title>ArcGIS Developer Guide: Snap to Roads</title>
<style>
html,
body,
#map {
padding: 0;
margin: 0;
height: 100%;
width: 100%;
}
.button-container {
position: absolute;
top: 15px;
right: 15px;
z-index: 1000;
background-color: #ffffff;
padding: 6px;
}
</style>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/[email protected]/ol.css"
type="text/css"
/>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/ol.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/olms.js"></script>
<link
rel="stylesheet"
href="https://unpkg.com/[email protected]/src/ol-popup.css"
/>
<script src="https://unpkg.com/[email protected]/dist/ol-popup.js"></script>
<script
type="module"
src="https://js.arcgis.com/calcite-components/3.2.0/calcite.esm.js"
></script>
</head>
<body>
<div class="button-container">
<calcite-button id="snapPointsButton">Snap GPS points</calcite-button>
<calcite-button
id="resetButton"
icon-start="refresh"
label="Reset application"
></calcite-button>
</div>
<div id="map"></div>
<script type="module">
/* Use for API key authentication */
const accessToken = "YOUR_ACCESS_TOKEN"
const snapToRoadsServiceURL =
"https://route-api.arcgis.com/arcgis/rest/services/World/SnapToRoadsSync/GPServer/SnapToRoads/execute"
const trackPointsURL = `https://services3.arcgis.com/GVgbJbqm8hXASVYi/arcgis/rest/services/EsriToRanchoCostcoEdit/FeatureServer/0`
const trackRouteURL = `https://services3.arcgis.com/GVgbJbqm8hXASVYi/arcgis/rest/services/ActualRouteLine/FeatureServer/0`
const basemapId = "arcgis/light-gray"
const basemapURL = `https://basemapstyles-api.arcgis.com/arcgis/rest/services/styles/v2/styles/${basemapId}?token=${accessToken}`
/**
* Returns a new layer created from a service
*/
const getVectorLayerFromFeatureService = (url, style) => {
const queryParams = new URLSearchParams({
where: "1=1",
outFields: "*",
returnGeometry: true,
f: "geojson",
token: accessToken,
})
const layerURL = `${url}/query?${queryParams.toString()}`
const layerSource = new ol.source.Vector({
format: new ol.format.GeoJSON(),
url: layerURL,
})
return new ol.layer.Vector({ source: layerSource, style })
}
/**
* Returns a new layer created from GeoJSON
*/
const getVectorLayer = (features, style) => {
const layerSource = new ol.source.Vector({
features,
})
const layer = new ol.layer.Vector({
source: layerSource,
style,
})
return layer
}
/**
* Update attribution
*/
const addAttribution = () => {
// Add Esri attribution
// Learn more in https://esriurl.com/attribution
const source = map
.getLayers()
.item(0)
.getSource()
const poweredByEsriString =
"Powered by <a href='https://www.esri.com/en-us/home' target='_blank'>Esri</a> | "
const attributionFn = source.getAttributions()
if (attributionFn) {
source.setAttributions(ViewStateLayerStateExtent => {
return [
poweredByEsriString,
...attributionFn(ViewStateLayerStateExtent),
]
})
} else source.setAttributions(poweredByEsriString)
}
/**
* Send request to the snap to roads service
*/
const snapPointsButtonWasClicked = async () => {
document.getElementById("snapPointsButton").loading = true
// remove any previous results
snappedRouteLayer.getSource().clear(true)
snappedPointsLayer.getSource().clear(true)
// get features
const sourceFeatures = trackPointsLayer.getSource().getFeatures()
// convert input features to Esri format
const esriFormat = new ol.format.EsriJSON()
const esriFeatures = esriFormat.writeFeatures(sourceFeatures, {
dataProjection: "EPSG:3857",
})
// add authorization
// const headerParams = {
// Authorization: `Bearer ${accessToken}`,
// }
// Service parameters
// https://developers.arcgis.com/rest/services-reference/enterprise/snap-to-roads/#required-parameters
const returnRoadProperties = JSON.stringify([
"posted_speed_limit_mph",
"length_miles",
])
const queryParams = new URLSearchParams({
f: "json",
token: accessToken,
points: esriFeatures,
return_lines: true,
return_location_fields: true,
road_properties_on_snapped_points: returnRoadProperties,
road_properties_on_lines: returnRoadProperties,
context: JSON.stringify({ outSR: { wkid: 3857 } }), // request returned features in the maps spatial reference
})
// send the request
const srvcResponse = await fetch(snapToRoadsServiceURL, {
method: "POST",
// headers: headerParams,
body: queryParams,
})
// get the response
const esriResults = await srvcResponse.json()
// convert results from Esri format to openlayers Features
const snappedPoints = esriFormat.readFeatures(
esriResults.results[0].value,
{ dataProjection: "EPSG:3857" }
)
const snappedLines = esriFormat.readFeatures(
esriResults.results[1].value,
{ dataProjection: "EPSG:3857" }
)
// add results to our sources
snappedPointsLayer.getSource().addFeatures(snappedPoints)
snappedRouteLayer.getSource().addFeatures(snappedLines)
document.getElementById("snapPointsButton").loading = false
}
const resetButtonWasClicked = async () => {
// remove any previous results
snappedRouteLayer.getSource().clear(true)
snappedPointsLayer.getSource().clear(true)
}
// Instantiate a new map object
const popup = new Popup()
const map = new ol.Map({ target: "map", overlays: [popup] })
// Set the maps initial center and scale level
map.setView(
new ol.View({
center: ol.proj.fromLonLat([-117.32366341353183, 34.06637751117044]),
zoom: 18,
})
)
/**
* use plugin to add our basemap style to the map
*/
await olms.apply(map, basemapURL)
addAttribution()
const trackRouteLayer = getVectorLayerFromFeatureService(trackRouteURL, {
"stroke-color": "rgba(0, 0, 0, 0.5)",
"stroke-width": 1,
"stroke-line-dash": [6, 6, 6, 6],
})
map.addLayer(trackRouteLayer)
const trackPointsLayer = getVectorLayerFromFeatureService(
trackPointsURL,
{
"circle-radius": 6,
"circle-fill-color": "rgba(0, 122, 194, 0.75)",
"circle-stroke-color": "#ffffff",
"circle-stroke-width": 2,
}
)
map.addLayer(trackPointsLayer)
const snappedRouteLayer = getVectorLayer([], {
"stroke-color": "rgba(0, 122, 194, 0.8)",
"stroke-width": 4,
})
map.addLayer(snappedRouteLayer)
const snappedPointsLayer = getVectorLayer([], {
"circle-radius": 4,
"circle-fill-color": "rgba(0, 0, 0, 0.8)",
"circle-stroke-color": "#ffffff",
"circle-stroke-width": 1,
})
map.addLayer(snappedPointsLayer)
const getPopupContent = forProps => {
const t = document.createElement("calcite-table")
t.striped = true
t.bordered = true
const hr = document.createElement("calcite-table-row")
hr.slot = "table-header"
t.appendChild(hr)
const nameth = document.createElement("calcite-table-header")
nameth.heading = "Name"
const valueth = document.createElement("calcite-table-header")
valueth.heading = "Value"
hr.appendChild(nameth)
hr.appendChild(valueth)
Object.keys(forProps).forEach(p => {
const r = document.createElement("calcite-table-row")
const nc = document.createElement("calcite-table-cell")
const vc = document.createElement("calcite-table-cell")
let fields = ["ObjectID", "posted_speed_limit_mph", "length_miles"]
if (p.geometry.getType() === "Point") {
fields = ["confidence", "line_id", ...fields]
} else {
fields = ["line_type", ...fields]
}
})
return t
}
// give some feedback if we are over a snapped route feature
map.on("pointermove", e => {
const featureHit = map.hasFeatureAtPixel(e.pixel, {
layerFilter: lyr =>
lyr === snappedRouteLayer || lyr === snappedPointsLayer,
})
map.getTargetElement().style.cursor = featureHit ? "pointer" : ""
})
// show feature properties
map.on("singleclick", async e => {
map.forEachFeatureAtPixel(
e.pixel,
(f, lyr) => {
console.log(lyr)
},
{
layerFilter: lyr =>
lyr === snappedRouteLayer || lyr === snappedPointsLayer,
}
)
// if (tl && tl.length > 0) {
// const props = getPopupContent(tl[0].getProperties())
// popup.show(e.coordinate, props)
// return
// }
// popup.hide()
})
// handle button click events
document
.getElementById("snapPointsButton")
.addEventListener("click", snapPointsButtonWasClicked)
document
.getElementById("resetButton")
.addEventListener("click", resetButtonWasClicked)
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment