-
-
Notifications
You must be signed in to change notification settings - Fork 259
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
245 additions
and
24 deletions.
There are no files selected for viewing
This file contains 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
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,6 +15,9 @@ | |
<link rel="stylesheet" type="text/css" href="https://unpkg.com/[email protected]/leaflet-coverage.css"> | ||
<script src="https://unpkg.com/[email protected]/dist/leaflet.js"></script> | ||
{% if data.type == "Coverage" or data.type == "CoverageCollection" %} | ||
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/[email protected]/c3.css"> | ||
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/d3.js"></script> | ||
<script src="https://cdn.jsdelivr.net/npm/[email protected]/c3.js"></script> | ||
<script src="https://unpkg.com/[email protected]/covutils.min.js"></script> | ||
<script src="https://unpkg.com/[email protected]/covjson-reader.src.js"></script> | ||
<script src="https://unpkg.com/[email protected]/leaflet-coverage.min.js"></script> | ||
|
@@ -27,42 +30,253 @@ | |
|
||
{% block body %} | ||
<section id="coverage"> | ||
<div id="items-map"></div> | ||
{% if data.features or data.coverages %} | ||
<div id="coverages-map"></div> | ||
{% else %} | ||
<div class="row col-sm-12"> | ||
<p>{% trans %}No items{% endtrans %}</p> | ||
</div> | ||
{% endif %} | ||
</section> | ||
{% endblock %} | ||
|
||
{% block extrafoot %} | ||
{% if data %} | ||
<script> | ||
var map = L.map('items-map').setView([{{ 45 }}, {{ -75 }}], 5); | ||
map.addLayer(new L.TileLayer( | ||
var map = L.map('coverages-map').setView([40, -85], 3); | ||
var baseLayers = { | ||
'Map': new L.TileLayer( | ||
'{{ config['server']['map']['url'] }}', { | ||
maxZoom: 18, | ||
attribution: '{{ config['server']['map']['attribution'] | safe }}' | ||
} | ||
)); | ||
}).addTo(map) | ||
} | ||
{% if data.type == "Coverage" or data.type == "CoverageCollection" %} | ||
var layers = L.control.layers(null, null, {collapsed: false}).addTo(map) | ||
|
||
CovJSON.read(JSON.parse('{{ data | to_json | safe }}')).then(function (cov) { | ||
cov.parameters.forEach((p) => { | ||
var layer = C.dataLayer(cov, {parameter: p.key}) | ||
.on('afterAdd', function () { | ||
C.legend(layer).addTo(map) | ||
map.fitBounds(layer.getBounds()) | ||
}) | ||
.addTo(map) | ||
layers.addOverlay(layer, p.observedProperty.label?.en) | ||
map.setZoom(5) | ||
let layerControl = L.control.layers(baseLayers, {}, {collapsed: false}).addTo(map) | ||
let layersInControl = new Set() | ||
let coverageLayersOnMap = new Set() | ||
let paramSync = new C.ParameterSync({ | ||
syncProperties: { | ||
palette: (p1, p2) => p1, | ||
paletteExtent: (e1, e2) => e1 && e2 ? [Math.min(e1[0], e2[0]), Math.max(e1[1], e2[1])] : null | ||
} | ||
}).on('parameterAdd', e => { | ||
// The virtual sync layer proxies the synced palette, paletteExtent, and parameter. | ||
// The sync layer will fire a 'remove' event if all real layers for that parameter were removed. | ||
let layer = e.syncLayer | ||
if (layer.palette) { | ||
C.legend(layer, { | ||
position: 'bottomright' | ||
}).addTo(map) | ||
} | ||
}) | ||
|
||
|
||
displayCovJSON(JSON.parse('{{ data | to_json | safe }}'), {display: true}) | ||
|
||
const truncateString = (str, maxLength) => { | ||
str = str.replace(/\+/g, ' '); | ||
return str.length > maxLength ? `${str.slice(0, maxLength - 3)}...` : str; | ||
}; | ||
|
||
function displayCovJSON(obj, options = {}) { | ||
map.fire('dataloading'); | ||
var layer = CovJSON.read(obj) | ||
.then(cov => { | ||
if (CovUtils.isDomain(cov)) { | ||
cov = CovUtils.fromDomain(cov); | ||
} | ||
|
||
map.fire('dataload'); | ||
|
||
// add each parameter as a layer | ||
let firstLayer; | ||
|
||
let layerClazz = C.dataLayerClass(cov); | ||
|
||
if (cov.coverages && !layerClazz) { | ||
// generic collection | ||
if (!cov.parameters) { | ||
throw new Error('only coverage collections with a "parameters" property are supported'); | ||
} | ||
|
||
for (let key of cov.parameters.keys()) { | ||
let layers = cov.coverages | ||
.filter(coverage => coverage.parameters.has(key)) | ||
.map(coverage => createLayer(coverage, { keys: [key] })); | ||
layers.forEach(layer => map.fire('covlayercreate', { layer })); | ||
let layerGroup = L.layerGroup(layers); | ||
layersInControl.add(layerGroup); | ||
layerControl.addOverlay(layerGroup, truncateString(key, 50)); | ||
if (!firstLayer) { | ||
firstLayer = layerGroup; | ||
// the following piece of code should be easier | ||
// TODO extend layer group class in leaflet-coverage (like PointCollection) to provide single 'add' event | ||
let addCount = 0; | ||
for (let l of layers) { | ||
l.on('afterAdd', () => { | ||
coverageLayersOnMap.add(l); | ||
++addCount; | ||
if (addCount === layers.length) { | ||
zoomToLayers(layers); | ||
// FIXME is this the right place?? define event semantics! | ||
map.fire('covlayeradd', { layer: l }); | ||
} | ||
}); | ||
} | ||
} | ||
} | ||
} else if (layerClazz) { | ||
// single coverage or a coverage collection of a specific domain type | ||
for (let key of cov.parameters.keys()) { | ||
let opts = { keys: [key] }; | ||
let layer = createLayer(cov, opts); | ||
map.fire('covlayercreate', { layer }); | ||
layersInControl.add(layer); | ||
|
||
layerControl.addOverlay(layer, truncateString(key, 50)); | ||
if (!firstLayer) { | ||
firstLayer = layer; | ||
layer.on('afterAdd', () => { | ||
zoomToLayers([layers]) | ||
if (!cov.coverages) { | ||
if (isVerticalProfile(cov) || isTimeSeries(cov)) { | ||
layer.openPopup(); | ||
} | ||
} | ||
}); | ||
} | ||
layer.on('afterAdd', () => { | ||
coverageLayersOnMap.add(layer); | ||
map.fire('covlayeradd', { layer }); | ||
}); | ||
} | ||
} else { | ||
throw new Error('unsupported or missing domain type'); | ||
} | ||
if (options.display && firstLayer) { | ||
map.addLayer(firstLayer); | ||
} | ||
}) | ||
.catch(e => { | ||
map.fire('dataload'); | ||
console.log(e); | ||
}); | ||
} | ||
|
||
function createLayer(cov, opts) { | ||
let layer = C.dataLayer(cov, opts).on('afterAdd', e => { | ||
let covLayer = e.target | ||
|
||
// This registers the layer with the sync manager. | ||
// By doing that, the palette and extent get unified (if existing) | ||
// and an event gets fired if a new parameter was added. | ||
// See the code above where ParameterSync gets instantiated. | ||
paramSync.addLayer(covLayer) | ||
|
||
if (!cov.coverages) { | ||
if (covLayer.time) { | ||
new C.TimeAxis(covLayer).addTo(map) | ||
} | ||
if (covLayer.vertical) { | ||
new C.VerticalAxis(covLayer).addTo(map) | ||
} | ||
} | ||
}).on('dataLoad', () => map.fire('dataload')) | ||
.on('dataLoading', () => map.fire('dataloading')) | ||
.on('error', e => map.fire('error', { error: e.error })) | ||
layer.on('axisChange', () => { | ||
layer.paletteExtent = 'subset' | ||
}) | ||
|
||
if (cov.coverages) { | ||
if (isVerticalProfile(cov)) { | ||
layer.bindPopupEach(coverage => new C.VerticalProfilePlot(coverage)) | ||
} else if (isTimeSeries(cov)) { | ||
layer.bindPopupEach(coverage => new C.TimeSeriesPlot(coverage)) | ||
} | ||
} else { | ||
if (isVerticalProfile(cov)) { | ||
layer.bindPopup(new C.VerticalProfilePlot(cov)) | ||
} else if (isTimeSeries(cov)) { | ||
layer.bindPopup(new C.TimeSeriesPlot(cov)) | ||
} | ||
} | ||
|
||
return layer | ||
} | ||
function removeLayers () { | ||
for (let layer of layersInControl) { | ||
layerControl.removeLayer(layer) | ||
if (map.hasLayer(layer)) { | ||
// FIXME leaflet's internal state breaks if layers or controls throw exceptions in onAdd() | ||
// -> could be prevented by linting CovJSON before-hand | ||
try { | ||
map.removeLayer(layer) | ||
} catch (e) {} | ||
} | ||
} | ||
layersInControl = new Set() | ||
} | ||
|
||
function zoomToLayers (layers) { | ||
let bnds = layers.map(l => l.getBounds()) | ||
let bounds = L.latLngBounds(bnds) | ||
let opts = { | ||
padding: L.point(10, 10) | ||
} | ||
if (bounds.getWest() === bounds.getEast() && bounds.getSouth() === bounds.getNorth()) { | ||
opts.maxZoom = 5 | ||
} | ||
map.fitBounds(bounds, opts) | ||
} | ||
|
||
function isVerticalProfile (cov) { | ||
return cov.domainType === C.COVJSON_VERTICALPROFILE | ||
} | ||
|
||
function isTimeSeries (cov) { | ||
return cov.domainType === C.COVJSON_POINTSERIES || cov.domainType === C.COVJSON_POLYGONSERIES | ||
} | ||
window.api = { | ||
map, | ||
layers: coverageLayersOnMap | ||
} | ||
|
||
// Wire up coverage value popup | ||
let valuePopup = new C.DraggableValuePopup({ | ||
className: 'leaflet-popup-draggable', | ||
layers: [...coverageLayersOnMap] | ||
}) | ||
|
||
map.on('click', function (e) { | ||
new C.DraggableValuePopup({ | ||
layers: [layer] | ||
}).setLatLng(e.latlng).openOn(map) | ||
function closeValuePopup () { | ||
if (map.hasLayer(valuePopup)) { | ||
map.closePopup(valuePopup) | ||
} | ||
} | ||
|
||
// click event needed for Grid layer (can't use bindPopup there) | ||
map.on('singleclick', e => { | ||
valuePopup.setLatLng(e.latlng).openOn(map) | ||
}) | ||
map.on('covlayercreate', e => { | ||
// some layers already have a plot popup bound to it, ignore those | ||
if (!e.layer.getPopup()) { | ||
e.layer.bindPopup(valuePopup) | ||
} | ||
}) | ||
map.on('covlayeradd', e => { | ||
valuePopup.addCoverageLayer(e.layer) | ||
}) | ||
map.on('covlayerremove', e => { | ||
valuePopup.removeCoverageLayer(e.layer) | ||
}) | ||
|
||
map.on('error', e => { | ||
if (e.error?.message) { | ||
editor.setError(e.error.message) | ||
} | ||
}) | ||
{% elif data.type == "Feature" or data.type == "FeatureCollection" %} | ||
var geojson_data = {{ data | to_json | safe }}; | ||
|
@@ -73,16 +287,14 @@ | |
layer.bindPopup(html); | ||
} | ||
}); | ||
{% if data.type == "FeatureCollection" and data['features'][0]['geometry']['type'] == 'Point' %} | ||
{% if data.type == "FeatureCollection" and data.features and data.features[0]['geometry']['type'] == 'Point' %} | ||
var markers = L.markerClusterGroup({ | ||
disableClusteringAtZoom: 9, | ||
chunkedLoading: true, | ||
chunkInterval: 500, | ||
}); | ||
markers.clearLayers().addLayer(items); | ||
map.addLayer(markers); | ||
{% else %} | ||
map.addLayer(items); | ||
{% endif %} | ||
map.fitBounds(items.getBounds(), {maxZoom: 15}); | ||
{% endif %} | ||
|