Skip to content

Instantly share code, notes, and snippets.

@kbatuigas
Created February 13, 2020 20:31
Show Gist options
  • Save kbatuigas/21b754c94c4d9f2f4d0eaef9f7944598 to your computer and use it in GitHub Desktop.
Save kbatuigas/21b754c94c4d9f2f4d0eaef9f7944598 to your computer and use it in GitHub Desktop.
Example of Portland area web map displaying neighborhood boundaries and transit stops - OpenLayers, pg_tileserv, pg_featureserv
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Portland</title>
<!-- CSS for OpenLayers map -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.2.0/css/ol.css" type="text/css">
<style>
html, body, #map-container {
margin: 0;
width: 100%;
height: 100%;
font-family: sans-serif;
}
#popup {
border-radius: 5px;
padding: 10px;
position: relative;
background: #fff;
border: 1px solid #000;
}
</style>
<!-- Include OpenLayers map -->
<script src="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.2.0/build/ol.js"></script>
</head>
<body>
<div id="map-container"></div>
<!-- popup also needs to be in DOM even though it isn't displayed until click -->
<div id="popup"></div>
<script>
/* Raster tiles for basemap */
const baseLayer = new ol.layer.Tile({
source: new ol.source.XYZ({
url: "https://maps.wikimedia.org/osm-intl/{z}/{x}/{y}.png"
})
});
/* Style neighborhood tiles so they stand out more */
const vectorStyle = new ol.style.Style({
stroke: new ol.style.Stroke({
width: 3,
color: 'red'
}),
fill: new ol.style.Fill({
color: 'rgba(255,0,0,0.1)'
})
});
// Vector tiles to display neighborhood boundaries
/* Neighborhood boundaries data from http://gis-pdx.opendata.arcgis.com/datasets/neighborhoods-regions */
const vectorServer = "http://localhost:7800/";
const vectorSourceLayer = "public.neighborhoods";
const vectorProps = "?properties=id,name,maplabel";
const vectorUrl = vectorServer + vectorSourceLayer + "/{z}/{x}/{y}.pbf" + vectorProps;
const vectorLayer = new ol.layer.VectorTile({
source: new ol.source.VectorTile({
format: new ol.format.MVT(),
url: vectorUrl
}),
style: vectorStyle
});
// Transit stops feature layer
// TriMet stops data from https://developer.trimet.org/gis/
/* TODO: Figure out why not all Portland stops seem to be displaying from
pg_fs (the Belmont Av ones are missing, for example), and why stops
outside of Portland are still displaying (e.g. Beaverton) even though
Portland jurisdiction has been specified below */
const featureSourceLayer = "public.tm_stops";
const LIMIT = "10000";
const jurisdic = "Portland";
const dataUrl = "http://localhost:9000/collections/" + featureSourceLayer + "/items.json?jurisdic=" +
jurisdic + "&limit=" + LIMIT;
const image = new ol.style.Circle({
radius: 5,
fill: new ol.style.Fill({ color: 'blue' }),
stroke: new ol.style.Stroke({
color: 'blue',
width: 1
})
});
const featureStyle = new ol.style.Style({
image: image
});
const featureLayer = new ol.layer.Vector({
source: new ol.source.Vector({
format: new ol.format.GeoJSON(),
url: dataUrl
}),
// TODO: Maybe follow pg_fs UI example and create a style function
style: featureStyle
});
/* Render map on page */
const map = new ol.Map({
target: 'map-container',
layers: [ baseLayer, vectorLayer ],
view: new ol.View({
// TODO: Figure out how to just neatly center the map
// on load based on whatever area is displayed
center: ol.proj.fromLonLat([-122.67, 45.54]),
zoom: 12
})
});
map.addLayer(featureLayer);
/* Widget displaying stop info */
const overlay = new ol.Overlay({
element: document.getElementById('popup'),
offset: [0, -5],
positioning: 'bottom-center'
});
map.addOverlay(overlay);
function displayFeatureInfo (evt) {
// Not sure why this is called first
overlay.setPosition();
// Why not map.forEachFeatureAtPixel?
let features = map.getFeaturesAtPixel(evt.pixel);
let loc = evt.coordinate;
let info = "";
if (features) {
// Neighborhood tiles also get returned as features;
// only display popup if it's a stop. Maybe there's
// a better way to do this
if (features[0].type_ === "Polygon") return;
info = features[0].get('stop_id') + ": " + features[0].get('stop_name');
overlay.getElement().innerHTML = info;
}
overlay.setPosition(loc);
}
map.on('click', function(evt) {
displayFeatureInfo(evt);
});
// ADD:
// 1. Different color or shape for bus vs MAX stop?
// 2. Filtering example?
// 3. Add transit lines?
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment