This commit is contained in:
parent
a271b43318
commit
fa135e1394
@ -4,10 +4,12 @@
|
|||||||
{% block title %}Sites · Run viewer{% endblock title %}
|
{% block title %}Sites · Run viewer{% endblock title %}
|
||||||
|
|
||||||
{% block extra_css %}
|
{% block extra_css %}
|
||||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
|
<link
|
||||||
integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin="">
|
rel="stylesheet"
|
||||||
|
href="https://cesium.com/downloads/cesiumjs/releases/1.114/Build/Cesium/Widgets/widgets.css"
|
||||||
|
/>
|
||||||
<style>
|
<style>
|
||||||
#sites-map { height: 70vh; border-radius: 0.5rem; }
|
#sites-map { height: 70vh; border-radius: 0.5rem; overflow: hidden; }
|
||||||
.layer-chip { margin-right: .75rem; }
|
.layer-chip { margin-right: .75rem; }
|
||||||
.leaflet-popup-content { margin: 10px 14px; }
|
.leaflet-popup-content { margin: 10px 14px; }
|
||||||
.scrub-wrap { display:flex; align-items:center; gap:.5rem; }
|
.scrub-wrap { display:flex; align-items:center; gap:.5rem; }
|
||||||
@ -69,6 +71,15 @@
|
|||||||
<label class="form-check-label" for="chkComp">Competition (sample)</label>
|
<label class="form-check-label" for="chkComp">Competition (sample)</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<label class="me-2">Basemap</label>
|
||||||
|
<select id="basemap" class="form-select form-select-sm" style="min-width: 180px;">
|
||||||
|
<option value="osm">OpenStreetMap</option>
|
||||||
|
<option value="esri">ESRI World Imagery</option>
|
||||||
|
<option value="carto_light">CARTO Light</option>
|
||||||
|
<option value="carto_dark">CARTO Dark</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -80,7 +91,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="text-muted small mt-1">
|
<div class="text-muted small mt-1">
|
||||||
Tip: hover candidates for score & breakdown; toggle layers on the right; use the scrubber to switch minutes.
|
Tip: click candidates for score & breakdown; toggle layers on the right; use the scrubber to switch minutes.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -89,8 +100,7 @@
|
|||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|
||||||
{% block extra_js %}
|
{% block extra_js %}
|
||||||
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"
|
<script src="https://cesium.com/downloads/cesiumjs/releases/1.114/Build/Cesium/Cesium.js"></script>
|
||||||
integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script>
|
|
||||||
<script>
|
<script>
|
||||||
(function(){
|
(function(){
|
||||||
const q = new URLSearchParams(window.location.search);
|
const q = new URLSearchParams(window.location.search);
|
||||||
@ -113,23 +123,93 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Base map
|
const viewer = new Cesium.Viewer('sites-map', {
|
||||||
const map = L.map('sites-map', { zoomControl: true });
|
imageryProvider: false,
|
||||||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
|
baseLayerPicker: false,
|
||||||
{ maxZoom: 19, attribution: '© OpenStreetMap' }).addTo(map);
|
geocoder: false,
|
||||||
|
homeButton: false,
|
||||||
|
navigationHelpButton: false,
|
||||||
|
sceneModePicker: false,
|
||||||
|
animation: false,
|
||||||
|
timeline: false,
|
||||||
|
infoBox: true,
|
||||||
|
selectionIndicator: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const basemapSelect = document.getElementById('basemap');
|
||||||
|
const basemapLayers = {};
|
||||||
|
|
||||||
|
function getBasemapProvider(key) {
|
||||||
|
if (key === 'osm') {
|
||||||
|
return new Cesium.OpenStreetMapImageryProvider({
|
||||||
|
url: 'https://tile.openstreetmap.org/',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (key === 'esri') {
|
||||||
|
return new Cesium.UrlTemplateImageryProvider({
|
||||||
|
url: 'https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}',
|
||||||
|
credit: 'Esri',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (key === 'carto_light') {
|
||||||
|
return new Cesium.UrlTemplateImageryProvider({
|
||||||
|
url: 'https://cartodb-basemaps-a.global.ssl.fastly.net/light_all/{z}/{x}/{y}.png',
|
||||||
|
credit: 'CARTO',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (key === 'carto_dark') {
|
||||||
|
return new Cesium.UrlTemplateImageryProvider({
|
||||||
|
url: 'https://cartodb-basemaps-a.global.ssl.fastly.net/dark_all/{z}/{x}/{y}.png',
|
||||||
|
credit: 'CARTO',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setBaseLayer(key) {
|
||||||
|
let layer = basemapLayers[key];
|
||||||
|
if (!layer) {
|
||||||
|
const provider = getBasemapProvider(key);
|
||||||
|
if (!provider) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
layer = viewer.imageryLayers.addImageryProvider(provider);
|
||||||
|
basemapLayers[key] = layer;
|
||||||
|
}
|
||||||
|
Object.keys(basemapLayers).forEach((k) => {
|
||||||
|
basemapLayers[k].show = k === key;
|
||||||
|
});
|
||||||
|
viewer.imageryLayers.raiseToTop(layer);
|
||||||
|
}
|
||||||
|
|
||||||
|
basemapSelect.addEventListener('change', () => {
|
||||||
|
setBaseLayer(basemapSelect.value);
|
||||||
|
});
|
||||||
|
setBaseLayer(basemapSelect.value);
|
||||||
|
|
||||||
|
viewer.scene.globe.depthTestAgainstTerrain = false;
|
||||||
|
|
||||||
// Layers
|
|
||||||
const layers = {
|
const layers = {
|
||||||
iso: L.layerGroup().addTo(map),
|
iso: new Map(),
|
||||||
cand: L.layerGroup().addTo(map),
|
cand: null,
|
||||||
demand: L.layerGroup(),
|
demand: null,
|
||||||
comp: L.layerGroup(),
|
comp: null,
|
||||||
};
|
};
|
||||||
document.getElementById('chkIso').addEventListener('change', (e)=> toggle(layers.iso, e.target.checked));
|
|
||||||
document.getElementById('chkCand').addEventListener('change', (e)=> toggle(layers.cand, e.target.checked));
|
let isoVisible = true;
|
||||||
document.getElementById('chkDemand').addEventListener('change', (e)=> toggle(layers.demand, e.target.checked));
|
document.getElementById('chkIso').addEventListener('change', (e)=> {
|
||||||
document.getElementById('chkComp').addEventListener('change', (e)=> toggle(layers.comp, e.target.checked));
|
isoVisible = e.target.checked;
|
||||||
function toggle(layer, on){ if (on) layer.addTo(map); else map.removeLayer(layer); }
|
renderMinute(currentIndex);
|
||||||
|
});
|
||||||
|
document.getElementById('chkCand').addEventListener('change', (e)=> {
|
||||||
|
if (layers.cand) layers.cand.show = e.target.checked;
|
||||||
|
});
|
||||||
|
document.getElementById('chkDemand').addEventListener('change', (e)=> {
|
||||||
|
if (layers.demand) layers.demand.show = e.target.checked;
|
||||||
|
});
|
||||||
|
document.getElementById('chkComp').addEventListener('change', (e)=> {
|
||||||
|
if (layers.comp) layers.comp.show = e.target.checked;
|
||||||
|
});
|
||||||
|
|
||||||
const isoURL = `/api/sites/geojson/isochrones/${encodeURIComponent(sid)}`;
|
const isoURL = `/api/sites/geojson/isochrones/${encodeURIComponent(sid)}`;
|
||||||
const candURL = `/api/sites/geojson/candidates/${encodeURIComponent(sid)}`;
|
const candURL = `/api/sites/geojson/candidates/${encodeURIComponent(sid)}`;
|
||||||
@ -137,12 +217,10 @@
|
|||||||
const compURL = `/api/sites/geojson/pois_competition/${encodeURIComponent(sid)}`;
|
const compURL = `/api/sites/geojson/pois_competition/${encodeURIComponent(sid)}`;
|
||||||
const artURL = `/media/sites/run_${encodeURIComponent(sid)}.json`;
|
const artURL = `/media/sites/run_${encodeURIComponent(sid)}.json`;
|
||||||
|
|
||||||
let fitBounds = null;
|
|
||||||
|
|
||||||
// 55A scrubber state
|
// 55A scrubber state
|
||||||
const bandColors = ['#2E86AB','#F18F01','#C73E1D','#6C5B7B','#17B890','#7E57C2'];
|
const bandColors = ['#2E86AB','#F18F01','#C73E1D','#6C5B7B','#17B890','#7E57C2'];
|
||||||
let minutes = [];
|
let minutes = [];
|
||||||
const isoGroups = new Map(); // minute -> LayerGroup
|
const isoGroups = new Map(); // minute -> array of features
|
||||||
const minuteAreas = new Map(); // minute -> total area_km2
|
const minuteAreas = new Map(); // minute -> total area_km2
|
||||||
let currentIndex = 0;
|
let currentIndex = 0;
|
||||||
let timer = null;
|
let timer = null;
|
||||||
@ -162,14 +240,13 @@
|
|||||||
function renderMinute(idx) {
|
function renderMinute(idx) {
|
||||||
currentIndex = Math.max(0, Math.min(idx, minutes.length - 1));
|
currentIndex = Math.max(0, Math.min(idx, minutes.length - 1));
|
||||||
const m = minutes[currentIndex];
|
const m = minutes[currentIndex];
|
||||||
minuteText.textContent = `${m} min`;
|
minuteText.textContent = m != null ? `${m} min` : '—';
|
||||||
areaText.textContent = fmtArea(minuteAreas.get(m));
|
areaText.textContent = fmtArea(minuteAreas.get(m));
|
||||||
|
|
||||||
layers.iso.clearLayers();
|
layers.iso.forEach((ds, minute)=>{
|
||||||
const group = isoGroups.get(m);
|
ds.show = isoVisible && minute === m;
|
||||||
if (group) layers.iso.addLayer(group);
|
});
|
||||||
|
|
||||||
// 55B: move chart marker
|
|
||||||
updateAreaMarker(m);
|
updateAreaMarker(m);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -274,7 +351,7 @@
|
|||||||
|
|
||||||
function updateAreaMarker(minute) {
|
function updateAreaMarker(minute) {
|
||||||
if (!chartState) return;
|
if (!chartState) return;
|
||||||
const { mins, areas, x, y, padT, H } = chartState;
|
const { mins, areas, x, y } = chartState;
|
||||||
const i = Math.max(0, mins.indexOf(minute));
|
const i = Math.max(0, mins.indexOf(minute));
|
||||||
const X = x(mins[i]);
|
const X = x(mins[i]);
|
||||||
const Y = y(areas[i]);
|
const Y = y(areas[i]);
|
||||||
@ -285,6 +362,51 @@
|
|||||||
if (dot) { dot.setAttribute('cx', X); dot.setAttribute('cy', Y); }
|
if (dot) { dot.setAttribute('cx', X); dot.setAttribute('cy', Y); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function extendBounds(bounds, lon, lat) {
|
||||||
|
if (!bounds) {
|
||||||
|
return { minLon: lon, minLat: lat, maxLon: lon, maxLat: lat };
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
minLon: Math.min(bounds.minLon, lon),
|
||||||
|
minLat: Math.min(bounds.minLat, lat),
|
||||||
|
maxLon: Math.max(bounds.maxLon, lon),
|
||||||
|
maxLat: Math.max(bounds.maxLat, lat),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function boundsFromGeometry(bounds, geom) {
|
||||||
|
if (!geom) return bounds;
|
||||||
|
if (geom.type === 'Point') {
|
||||||
|
return extendBounds(bounds, geom.coordinates[0], geom.coordinates[1]);
|
||||||
|
}
|
||||||
|
if (geom.type === 'Polygon') {
|
||||||
|
geom.coordinates.forEach(ring => {
|
||||||
|
ring.forEach(coord => { bounds = extendBounds(bounds, coord[0], coord[1]); });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (geom.type === 'MultiPolygon') {
|
||||||
|
geom.coordinates.forEach(poly => {
|
||||||
|
poly.forEach(ring => {
|
||||||
|
ring.forEach(coord => { bounds = extendBounds(bounds, coord[0], coord[1]); });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return bounds;
|
||||||
|
}
|
||||||
|
|
||||||
|
function flyToBounds(bounds) {
|
||||||
|
if (!bounds) return;
|
||||||
|
const rect = Cesium.Rectangle.fromDegrees(bounds.minLon, bounds.minLat, bounds.maxLon, bounds.maxLat);
|
||||||
|
viewer.camera.flyTo({ destination: rect });
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadGeoJson(fc, options) {
|
||||||
|
if (!fc || !fc.features || fc.features.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return Cesium.GeoJsonDataSource.load(fc, options);
|
||||||
|
}
|
||||||
|
|
||||||
// Fetch everything
|
// Fetch everything
|
||||||
Promise.allSettled([
|
Promise.allSettled([
|
||||||
fetch(isoURL).then(r=>r.ok?r.json():null),
|
fetch(isoURL).then(r=>r.ok?r.json():null),
|
||||||
@ -292,32 +414,45 @@
|
|||||||
fetch(demURL).then(r=>r.ok?r.json():null),
|
fetch(demURL).then(r=>r.ok?r.json():null),
|
||||||
fetch(compURL).then(r=>r.ok?r.json():null),
|
fetch(compURL).then(r=>r.ok?r.json():null),
|
||||||
fetch(artURL).then(r=>r.ok?r.json():null)
|
fetch(artURL).then(r=>r.ok?r.json():null)
|
||||||
]).then(([iso, cand, dem, comp, art])=>{
|
]).then(async ([iso, cand, dem, comp, art])=>{
|
||||||
const isoFC = iso.value || {type:'FeatureCollection',features:[]};
|
const isoFC = iso.value || {type:'FeatureCollection',features:[]};
|
||||||
const candFC = cand.value|| {type:'FeatureCollection',features:[]};
|
const candFC = cand.value|| {type:'FeatureCollection',features:[]};
|
||||||
const demFC = dem.value || {type:'FeatureCollection',features:[]};
|
const demFC = dem.value || {type:'FeatureCollection',features:[]};
|
||||||
const compFC = comp.value|| {type:'FeatureCollection',features:[]};
|
const compFC = comp.value|| {type:'FeatureCollection',features:[]};
|
||||||
const artObj = art.value || null;
|
const artObj = art.value || null;
|
||||||
|
|
||||||
// Fit bounds to all isochrones
|
let bounds = null;
|
||||||
const allIso = L.geoJSON(isoFC);
|
|
||||||
const b = allIso.getBounds();
|
|
||||||
if (b.isValid()) { map.fitBounds(b.pad(0.05)); fitBounds = b; }
|
|
||||||
|
|
||||||
// Build minute groups + areas
|
// Build minute groups + areas
|
||||||
(isoFC.features || []).forEach((f)=>{
|
(isoFC.features || []).forEach((f)=>{
|
||||||
const m = (f.properties && Number(f.properties.minutes)) || 0;
|
const m = (f.properties && Number(f.properties.minutes)) || 0;
|
||||||
if (!isoGroups.has(m)) isoGroups.set(m, L.layerGroup());
|
if (!isoGroups.has(m)) isoGroups.set(m, []);
|
||||||
const i = Math.max(0, Math.floor(m/5) - 1);
|
isoGroups.get(m).push(f);
|
||||||
const c = bandColors[i % bandColors.length];
|
|
||||||
const lyr = L.geoJSON(f, { style: { color:c, weight:2, fillColor:c, fillOpacity:.25 } });
|
|
||||||
isoGroups.get(m).addLayer(lyr);
|
|
||||||
const a = Number((f.properties && f.properties.area_km2) || 0);
|
const a = Number((f.properties && f.properties.area_km2) || 0);
|
||||||
minuteAreas.set(m, (minuteAreas.get(m) || 0) + (isFinite(a) ? a : 0));
|
minuteAreas.set(m, (minuteAreas.get(m) || 0) + (isFinite(a) ? a : 0));
|
||||||
|
bounds = boundsFromGeometry(bounds, f.geometry);
|
||||||
});
|
});
|
||||||
|
|
||||||
minutes = Array.from(isoGroups.keys()).sort((a,b)=>a-b);
|
minutes = Array.from(isoGroups.keys()).sort((a,b)=>a-b);
|
||||||
|
|
||||||
|
for (const minute of minutes) {
|
||||||
|
const i = Math.max(0, Math.floor(minute/5) - 1);
|
||||||
|
const colorHex = bandColors[i % bandColors.length];
|
||||||
|
const color = Cesium.Color.fromCssColorString(colorHex);
|
||||||
|
const fc = { type: 'FeatureCollection', features: isoGroups.get(minute) || [] };
|
||||||
|
const ds = await loadGeoJson(fc, {
|
||||||
|
stroke: color,
|
||||||
|
fill: color.withAlpha(0.25),
|
||||||
|
strokeWidth: 2,
|
||||||
|
clampToGround: true,
|
||||||
|
});
|
||||||
|
if (ds) {
|
||||||
|
ds.show = false;
|
||||||
|
viewer.dataSources.add(ds);
|
||||||
|
layers.iso.set(minute, ds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 55B: draw chart if ≥2 bands
|
// 55B: draw chart if ≥2 bands
|
||||||
if (minutes.length >= 2) {
|
if (minutes.length >= 2) {
|
||||||
const areas = minutes.map(m => minuteAreas.get(m) || 0);
|
const areas = minutes.map(m => minuteAreas.get(m) || 0);
|
||||||
@ -340,49 +475,75 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Candidates
|
// Candidates
|
||||||
L.geoJSON(candFC, {
|
layers.cand = await loadGeoJson(candFC, { clampToGround: true });
|
||||||
pointToLayer: (f, latlng)=>{
|
if (layers.cand) {
|
||||||
const s = Number(f.properties?.score ?? 0.5);
|
viewer.dataSources.add(layers.cand);
|
||||||
const r = 6 + Math.round(s * 10);
|
layers.cand.entities.values.forEach((entity)=>{
|
||||||
return L.circleMarker(latlng, { radius: r, weight: 2, color: '#111', fillOpacity: 0.9 });
|
const score = entity.properties?.score?.getValue(Cesium.JulianDate.now()) || 0;
|
||||||
},
|
const rank = entity.properties?.rank?.getValue(Cesium.JulianDate.now()) || '—';
|
||||||
onEachFeature: (f, layer)=>{
|
const demand = entity.properties?.demand?.getValue(Cesium.JulianDate.now()) || 0;
|
||||||
const p = f.properties || {};
|
const competition = entity.properties?.competition?.getValue(Cesium.JulianDate.now()) || 0;
|
||||||
const score = Number(p.score ?? 0).toFixed(2);
|
const access = entity.properties?.access?.getValue(Cesium.JulianDate.now()) || 0;
|
||||||
const br = `Demand ${Number(p.demand||0).toFixed(2)}, `
|
const radius = 6 + Math.round(score * 10);
|
||||||
+ `Comp ${Number(p.competition||0).toFixed(2)}, `
|
entity.point = new Cesium.PointGraphics({
|
||||||
+ `Access ${Number(p.access||0).toFixed(2)}`;
|
pixelSize: radius,
|
||||||
layer.bindPopup(`<b>Rank ${p.rank || '—'}</b> · score ${score}<br><small>${br}</small>`);
|
color: Cesium.Color.fromCssColorString('#1B998B'),
|
||||||
|
outlineColor: Cesium.Color.BLACK.withAlpha(0.7),
|
||||||
|
outlineWidth: 1,
|
||||||
|
});
|
||||||
|
entity.description = `<b>Rank ${rank}</b> · score ${Number(score).toFixed(2)}<br>` +
|
||||||
|
`<small>Demand ${Number(demand).toFixed(2)}, ` +
|
||||||
|
`Comp ${Number(competition).toFixed(2)}, ` +
|
||||||
|
`Access ${Number(access).toFixed(2)}</small>`;
|
||||||
|
});
|
||||||
|
if (!bounds) {
|
||||||
|
candFC.features.forEach((f)=>{ bounds = boundsFromGeometry(bounds, f.geometry); });
|
||||||
}
|
}
|
||||||
}).addTo(layers.cand);
|
|
||||||
|
|
||||||
if (!fitBounds) {
|
|
||||||
const b2 = L.geoJSON(candFC).getBounds();
|
|
||||||
if (b2.isValid()) map.fitBounds(b2.pad(0.1));
|
|
||||||
else map.setView([19.4326, -99.1332], 11);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Demand & Competition (start hidden)
|
// Demand & Competition (start hidden)
|
||||||
L.geoJSON(demFC, {
|
layers.demand = await loadGeoJson(demFC, { clampToGround: true });
|
||||||
pointToLayer: (f, latlng)=> L.circleMarker(latlng, { radius: 3, color: '#d9534f', weight: 0, fillOpacity: .7 }),
|
if (layers.demand) {
|
||||||
onEachFeature: (f, layer)=>{
|
viewer.dataSources.add(layers.demand);
|
||||||
const pop = f.properties?.pop;
|
layers.demand.show = false;
|
||||||
if (pop) layer.bindPopup(`Population: ${Math.round(pop)}`);
|
layers.demand.entities.values.forEach((entity)=>{
|
||||||
}
|
entity.point = new Cesium.PointGraphics({
|
||||||
}).addTo(layers.demand);
|
pixelSize: 4,
|
||||||
L.geoJSON(compFC, {
|
color: Cesium.Color.fromCssColorString('#d9534f'),
|
||||||
pointToLayer: (f, latlng)=> L.circleMarker(latlng, { radius: 3, color: '#0d6efd', weight: 0, fillOpacity: .7 }),
|
outlineColor: Cesium.Color.BLACK.withAlpha(0.4),
|
||||||
onEachFeature: (f, layer)=>{
|
outlineWidth: 1,
|
||||||
const name = f.properties?.name || '(poi)';
|
});
|
||||||
const cat = f.properties?.category || '';
|
});
|
||||||
layer.bindPopup(`${name}${cat?' — '+cat:''}`);
|
}
|
||||||
}
|
|
||||||
}).addTo(layers.comp);
|
layers.comp = await loadGeoJson(compFC, { clampToGround: true });
|
||||||
map.removeLayer(layers.demand);
|
if (layers.comp) {
|
||||||
map.removeLayer(layers.comp);
|
viewer.dataSources.add(layers.comp);
|
||||||
|
layers.comp.show = false;
|
||||||
|
layers.comp.entities.values.forEach((entity)=>{
|
||||||
|
const name = entity.properties?.name?.getValue(Cesium.JulianDate.now()) || '(poi)';
|
||||||
|
const cat = entity.properties?.category?.getValue(Cesium.JulianDate.now()) || '';
|
||||||
|
entity.point = new Cesium.PointGraphics({
|
||||||
|
pixelSize: 4,
|
||||||
|
color: Cesium.Color.fromCssColorString('#0d6efd'),
|
||||||
|
outlineColor: Cesium.Color.BLACK.withAlpha(0.4),
|
||||||
|
outlineWidth: 1,
|
||||||
|
});
|
||||||
|
entity.description = `${name}${cat ? ' — ' + cat : ''}`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
document.getElementById('chkDemand').checked = false;
|
document.getElementById('chkDemand').checked = false;
|
||||||
document.getElementById('chkComp').checked = false;
|
document.getElementById('chkComp').checked = false;
|
||||||
|
|
||||||
|
if (bounds) {
|
||||||
|
flyToBounds(bounds);
|
||||||
|
} else {
|
||||||
|
viewer.camera.flyTo({
|
||||||
|
destination: Cesium.Cartesian3.fromDegrees(-99.1332, 19.4326, 14000),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Meta (best-effort via artifact)
|
// Meta (best-effort via artifact)
|
||||||
if (artObj && artObj.request) {
|
if (artObj && artObj.request) {
|
||||||
const req = artObj.request;
|
const req = artObj.request;
|
||||||
@ -397,7 +558,7 @@
|
|||||||
let _rt = null;
|
let _rt = null;
|
||||||
window.addEventListener('resize', ()=>{
|
window.addEventListener('resize', ()=>{
|
||||||
if (!_rt) {
|
if (!_rt) {
|
||||||
_rt = requestAnimationFrame(()=>{
|
_rt = requestAnimationFrame(()=>{
|
||||||
if (minutes.length >= 2) {
|
if (minutes.length >= 2) {
|
||||||
const areas = minutes.map(m => minuteAreas.get(m) || 0);
|
const areas = minutes.map(m => minuteAreas.get(m) || 0);
|
||||||
drawAreaChart(minutes, areas);
|
drawAreaChart(minutes, areas);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user