Commit 46f6307d authored by Rodrigo Tapia-McClung's avatar Rodrigo Tapia-McClung

Test base water bodies

parent ef94540a
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Contenido de agua en la cuenca Grijalva</title>
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no" />
<link rel="stylesheet" href="http://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css" />
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.5.1/dist/leaflet.css" />
<link rel="stylesheet" href="https://cdn.rawgit.com/socib/Leaflet.TimeDimension/master/dist/leaflet.timedimension.control.min.css" />
<link rel="stylesheet" href="https://api.tiles.mapbox.com/mapbox-gl-js/v1.1.0/mapbox-gl.css" />
<link rel="stylesheet" href="../js//Leaflet.BeautifyIcon/leaflet-beautify-marker-icon.css">
<link rel="stylesheet" href="../js/L.VisualClick/L.VisualClick.css">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/1.0.4/leaflet.draw.css" />
<link rel="stylesheet" href="../css/style.css">
<link rel="stylesheet" href="../css/fontawesome.css" />
<link rel="icon" href="../img/grijalva.png" sizes="16x16">
</head>
<body>
<div id="initial-backdrop">
<img class="img_bg" src="../img/sumidero2.jpg" alt="Ca&ntilde;&oacute;n del Sumidero">
<span class="photo-credit">Foto: Gabriela L&oacute;pez</span>
</div>
<div class="container" id="startHeader">
<div class="jumbotron mt-5" style="background-color: rgba(233,236,239,0.75); opacity: 0.99;">
<h1 class="display-4">Contenido de agua en la cuenca del r&iacute;o Grijalva</h1>
<p class="lead">Explora la apariencia de la cuenca del r&iacute;o Grijalva a trav&eacute;s del tiempo
por medio de la visualizaci&oacute;n de cuerpos de agua y
algunos indicadores obtenidos del an&aacute;lisis de im&aacute;genes de radar de Sentinel-1.</p>
<hr class="my-4">
<p>Escoge las fechas para las que quieres explorar el contenido de agua y sus indicadores.</p>
<div class="container">
<div class="row justify-content-center">
<div class="col-4 text-center">
<input type="text" name="date-initial" id="date-initial" readonly="readonly" size="12"
placeholder="Fecha inicial" data-calendar="false" />
</div>
<div class="col-4 text-center">
<input type="text" name="date-final" id="date-final" readonly="readonly" size="12"
placeholder="Fecha final" data-calendar="true" />
</div>
</div>
</div>
</div>
</div>
<div class="d-flex flex-column" id="mainContainer">
<header class="page-header py-2">
<div class="container text-center">
<div class="row align-items-center text-center" id="title">
<h2>Contenido de agua en la cuenca del r&iacute;o Grijalva</h2>
</div>
</div>
</header>
<!-- middle container -->
<div class="container-fluid d-flex flex-fill">
<div class="row d-flex flex-fill">
<div class="col-md-6" id="mexmap">
<div id="datePickers"></div>
<div class="picker">
<select id="indicatorSelect"></select>
</div>
<div class="text-center loader">
<i class="fa fa-spinner2 fa-spin fa-5x loadSpinner"></i><br>
<span class="loadText">Cargando datos...<br>
<span id="wb-fileLoad"></span>
<span id="basemap-fileLoad"></span>
</span>
</div>
</div>
<div class="col-md-6" id="overlaydiv"></div>
<div class="col-md-6" id="story">
<div class="loader"></div>
<div class="row h-50 border-bottom">
<div class="col-6 border-right">
<div id="areacpo-graph"></div>
</div>
<div class="col-6">
<div id="perimcpo-graph"></div>
</div>
</div>
<div class="row h-50">
<div class="col-6 border-right">
<div id="dlccpo-graph"></div>
</div>
<div class="col-6">
<div id="dimfrcpo-graph"></div>
</div>
</div>
</div>
</div>
</div>
<footer class="page-footer py-2">
<div class="container-fluid text-center">
<div class="row align-items-center">
<div class="col">
<span id="logo-conacyt"><a href="http://www.concayt.gob.mx" target="_blank"><img
src="../img/conacyt3.png" height="70" class="grayscale"></a></span>
</div>
<div class="col">
<span id="logo-cg"><a href="http://www.centrogeo.org.mx" target="_blank"><img
src="../img/centrogeo.png" height="70" class="grayscale"></a></span>
</div>
<div class="col">
<span id="logo-geoint"><a href="http://geoint.mx/" target="_blank"><img
src="../img/geoint.png" height="70" class="grayscale"></a></span>
</div>
</div>
</div>
</footer>
</div>
<!-- Modal -->
<div class="modal fade" id="explainIndicatorModal" tabindex="-1" role="dialog"
aria-labelledby="explainIndicatorModal" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="explainIndicatorModalTitle">Modal title</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body"></div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" data-dismiss="modal">Cerrar</button>
</div>
</div>
</div>
</div>
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
<script src="https://unpkg.com/leaflet@1.5.1/dist/leaflet.js"></script>
<script src="../js/Leaflet.BeautifyIcon/leaflet-beautify-marker-icon.js"></script>
<script src="../js/L.VisualClick/L.VisualClick.js"></script>
<script src="https://api.mapbox.com/mapbox.js/plugins/leaflet-omnivore/v0.3.1/leaflet-omnivore.min.js"></script>
<script src="https://cdn.rawgit.com/nezasa/iso8601-js-period/master/iso8601.min.js"></script>
<script src="https://cdn.rawgit.com/socib/Leaflet.TimeDimension/master/dist/leaflet.timedimension.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/chroma-js/2.0.3/chroma.min.js"></script>
<script src="https://d3js.org/d3.v5.min.js"></script>
<script src="https://api.tiles.mapbox.com/mapbox-gl-js/v1.1.0/mapbox-gl.js"></script>
<script src="https://unpkg.com/mapbox-gl-leaflet/leaflet-mapbox-gl.js"></script>
<script src="../js/Leaflet.Sync.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/1.0.4/leaflet.draw.js"></script>
<script src="../js/jszip.min.js"></script>
<script src="../js/cuerpos_functions.js"></script>
<script src="../js/cuerpos_basemap.js"></script>
<script src="../js/cuerpos_charts.js"></script>
</body>
</html>
\ No newline at end of file
/*
* Copyright 2019 - All rights reserved.
* Rodrigo Tapia-McClung
*
* August-September 2019
*/
/* global baseFileSize, formatBytes, Promise, omnivore, JSZip, map, layerControl, updateCharts */
/* exported makeBaseMap, baseLayerPromises, drawnItems */
/* Lines related to displaying loading bar are commented */
let baseLayerPromises = [];
/* baseSize = 0,
baseDisplaySize,
baseLoadedSize = 0,
baseLoadedDisplaySize,
baseLayerCounter = 0,
currentBaseLayer = 1;*/
let drawnItems;
/*Object.keys(baseFileSize).forEach((name) => {
if (name.split(".")[1] == "zip") {
baseSize += parseFloat(baseFileSize[name].size); // file size to load for zipped base layers
baseDisplaySize = formatBytes(baseSize, 2); // size in pretty units
baseLayerCounter++;
}
});*/
// function to read compressed json and create a leaflet layer
const zip2Lyr = (zipFile, layerName, layerTemplate) => {
baseLayerPromises.push(
new Promise( (resolve, reject) => {
fetch(zipFile) // 1) fetch the url
.then( response => { // 2) filter on 200 OK
if (response.status === 200 || response.status === 0) {
return Promise.resolve(response.blob());
} else {
return Promise.reject(new Error(response.statusText));
}
})
.then(JSZip.loadAsync) // 3) chain with the zip promise
.then(zip => {
let filename = Object.keys(zip.files)[0];
let filetype = filename.split(".").pop();
return [zip.file(filename), filetype]; // 4) chain with the text content promise
})
.then( ([file, type]) => {
file.async("string").then(
function success(text) { // 5) display the result
/*let baseFile = zipFile.split("/")[3];
¬baseLoadedSize += parseFloat(baseFileSize[baseFile].size); // cumulative loaded size
baseLoadedDisplaySize = formatBytes(baseLoadedSize); // in pretty units
let baseBarWidth = (baseLoadedSize/baseSize*100);*/
/*currentBaseLayer++;
let baseFiles = currentBaseLayer <= baseLayerCounter ? currentBaseLayer : baseLayerCounter;
$("#basemap-fileLoad").html("Cargando capa " + baseFiles + " de " + baseLayerCounter + "<br>" +
"<span class=\"progress\"><span id=\"baseBar\" class=\"progress-bar progress-bar-striped progress-bar-animated\" role=\"progressbar\" style=\"width: " + baseBarWidth + "%;\" aria-valuenow=\"" + baseBarWidth + "\" aria-valuemin=\"0\" aria-valuemax=\"100\"></span>" +
"<span class=\"justify-content-center d-flex position-absolute w-100\">" + baseLoadedDisplaySize + "/" + baseDisplaySize + "</span></span>");
*/if (type == "json") {
layerTemplate.addData(JSON.parse(text));
} else if (type == "topojson") {
layerName = omnivore.topojson.parse(text, null, layerTemplate);
}
resolve("done");
}, function error(e) {
console.log(e);
reject("error");
}
);
});
})
);
}
const createPane = (layerPane, zIndex) => {
map.createPane(layerPane);
map.getPane(layerPane).style.zIndex = zIndex;
map.getPane(layerPane).style["mix-blend-mode"] = "normal";
}
const getSchoolColor = s => {
return s == "Básico" ? "#409400" :
s == "Medio superior" ? "#df6400" :
"#c0d1e5";
}
let escuelas, hospitales, hoteles, puentes, puntoFronterizo, descargas, presas, subcuencas, tuxtlaVH, zms, anps;
// *** WMS INEGI ***
// AGEBS RURAL
let agebrur = new L.tileLayer.wms("http://gaia.inegi.org.mx/NLB/tunnel/wms/wms61?", {
layers: "c108",
format: "image/svg+xml",
transparent: true,
attribution: "INEGI 2019"
});
// AGEBS URBANO
let ageburb = new L.tileLayer.wms("http://gaia.inegi.org.mx/NLB/tunnel/wms/wms61?", {
layers: "c109",
format: "image/svg+xml",
transparent: true,
attribution: "INEGI 2019"
});
let layer_agebswms = L.layerGroup([agebrur, ageburb],{
pane: "pane_agebs",
minZoom:11,
maxZoom:18
});
// NOMBRES DE CUERPOS DE AGUA
let layer_nombresWB = new L.tileLayer.wms("http://gaia.inegi.org.mx/NLB/tunnel/wms/wms61?", {
layers: "t311",
format: "image/svg+xml",
transparent: true,
attribution: "INEGI 2019",
minZoom:9,
pane: "pane_wbnames"
});
//RED NACIONAL DE CAMINOS
let layer_RedNacionalCaminos= new L.tileLayer.wms("http://gaia.inegi.org.mx/NLB/tunnel/wms/wms61?", {
layers: "RNC", //Red nacional de caminos
format: "image/svg+xml",
transparent: true,
pane: "pane_carreteras",
attribution: "INEGI 2019"
});
//MANZANAS
let layer_manzanaswms= new L.tileLayer.wms("http://gaia.inegi.org.mx/NLB/tunnel/wms/wms61?", {
layers: "c104",
format: "image/svg+xml",
transparent: true,
pane: "pane_manzanas",
attribution: "INEGI 2019",
minZoom:13,
maxZoom:18
});
//LOCALIDAD URBANA
let layer_locUrban= new L.tileLayer.wms("http://gaia.inegi.org.mx/NLB/tunnel/wms/wms61?", {
layers: "c102",
format: "image/svg+xml",
transparent: true,
pane: "pane_locs",
attribution: "INEGI 2019"
});
//Límite geoestadístico municipal
let layer_limiteMunicipal= new L.tileLayer.wms("http://gaia.inegi.org.mx/NLB/tunnel/wms/wms61?", {
layers: "c101", //Límite geoestadístico municipal
format: "image/svg+xml",
transparent: true,
pane: "pane_munis",
attribution: "INEGI 2019",
minZoom:9
});
// Area verde urbana -- camellones?
let layer_areaVerdeUrbana= new L.tileLayer.wms("http://gaia.inegi.org.mx/NLB/tunnel/wms/wms61?", {
layers: "c110", //Límite geoestadístico municipal
format: "image/svg+xml",
transparent: true,
pane: "pane_camellones",
attribution: "INEGI 2019",
minZoom:11,
maxZoom:18
});
// *** FIN WMS INEGI ***
let layer_escuelas = new L.geoJson(null, {
attribution: "",
pane: "pane_escuelas",
//onEachFeature: pop_escuelas,
pointToLayer: (feature, latlng) => {
// let context = {
// feature: feature,
// variables: {}
// };
let escuelaMarker = L.BeautifyIcon.icon({
icon: "escuela",
iconSize: [0, 0],
iconAnchor: [7, 10],
iconShape: "circle",
popupAnchor: [0, 0],
innerIconStyle: "font-size: 15px;",
borderWidth: 0,
borderColor: "transparent",
backgroundColor: "transparent",
textColor: getSchoolColor(feature.properties["Nivel"])
});
return new L.Marker(latlng, {
icon: escuelaMarker
});
}
});
let layer_hospitales = new L.geoJson(null, {
attribution: "",
pane: "pane_hospitales",
//onEachFeature: pop_hospitales,
pointToLayer: (_feature, latlng) => {
// let context = {
// feature: feature,
// variables: {}
// };
let hospitalMarker = L.BeautifyIcon.icon({
icon: "desechos",
iconSize: [0, 0],
iconAnchor: [7, 10],
iconShape: "circle",
popupAnchor: [0, 0],
innerIconStyle: "font-size: 13px;",
html: "<span class=\"fa-stack\"><i class=\"fas fa-desechos fa-stack-1x\" style=\"color:#ff0000;\"></i><i class =\"fas fa-plus-circle fa-stack-1x fa-inverse\"></i></span>",
borderWidth: 0,
borderColor: "transparent",
backgroundColor: "transparent",
textColor: "#C0C0C0"
});
return new L.Marker(latlng, {
icon: hospitalMarker
});
}
});
let layer_hoteles = new L.geoJson(null, {
attribution: "",
pane: "pane_hoteles",
//onEachFeature: pop_hoteles,
pointToLayer: (_feature, latlng) => {
// let context = {
// feature: feature,
// variables: {}
// };
let hotelMarker = L.BeautifyIcon.icon({
icon: "bed",
iconSize: [0, 0],
iconAnchor: [7, 10],
iconShape: "circle",
popupAnchor: [0, 0],
innerIconStyle: "font-size: 12px;",
borderWidth: 0,
borderColor: "transparent",
backgroundColor: "transparent",
textColor: "#808080"
});
return new L.Marker(latlng, {
icon: hotelMarker
});
}
});
let layer_puentes = new L.geoJson(null, {
attribution: "",
pane: "pane_puentes",
//onEachFeature: pop_puentes,
pointToLayer: (_feature, latlng) => {
// var context = {
// feature: feature,
// variables: {}
// };
let puenteMarker = L.BeautifyIcon.icon({
// icon: "grip-lines-vertical",
icon: "puente",
iconSize: [0, 0],
iconAnchor: [7, 10],
iconShape: "circle",
popupAnchor: [0, 0],
innerIconStyle: "font-size: 12px; transform: rotate(45deg);",
borderWidth: 0,
borderColor: "transparent",
backgroundColor: "transparent",
textColor: "#808080"
});
return new L.Marker(latlng, {
icon: puenteMarker
});
}
});
let layer_puerto = new L.geoJson(null, {
attribution: "",
pane: "pane_puerto",
//onEachFeature: pop_puerto,
pointToLayer: (_feature, latlng) => {
// let context = {
// feature: feature,
// variables: {}
// };
let puertoMarker = L.BeautifyIcon.icon({
icon: "puertoindustrial",
iconSize: [0, 0],
iconAnchor: [7, 10],
iconShape: "circle",
popupAnchor: [0, 0],
innerIconStyle: "font-size: 20px;",
borderWidth: 0,
borderColor: "transparent",
backgroundColor: "transparent",
textColor: "grey"
});
return new L.Marker(latlng, {
icon: puertoMarker
});
}
});
let layer_frontera = new L.geoJson(null, {
attribution: "",
pane: "pane_frontera",
//onEachFeature: pop_frontera,
styles: null,
pointToLayer: (_feature, latlng) => {
// let context = {
// feature: feature,
// variables: {}
// };
let fronteraMarker = L.BeautifyIcon.icon({
icon: "puntofronterizo",
iconSize: [0, 0],
iconAnchor: [7, 10],
iconShape: "circle",
popupAnchor: [0, 0],
innerIconStyle: "font-size: 14px;",
borderWidth: 0,
borderColor: "transparent",
backgroundColor: "transparent",
textColor: "#808080"
});
return new L.Marker(latlng, {
icon: fronteraMarker
});
}
});
let layer_descargas = new L.geoJson(null, {
attribution: "",
pane: "pane_descargas",
//onEachFeature: pop_descargas,
pointToLayer: (_feature, latlng) => {
// let context = {
// feature: feature,
// variables: {}
// };
let descargasMarker = L.BeautifyIcon.icon({
icon: "desechos",
iconSize: [0, 0],
iconAnchor: [7, 10],
iconShape: "circle",
popupAnchor: [0, 0],
innerIconStyle: "font-size: 8px; transform: rotate(45deg); opacity:0.75;",
borderWidth: 0,
borderColor: "black",
backgroundColor: "transparent",
textColor: "#808080"
});
return new L.Marker(latlng, {
icon: descargasMarker
});
}
});
let layer_presas = new L.geoJson(null, {
attribution: "",
pane: "pane_presas",
//onEachFeature: pop_presas,
pointToLayer: (_feature, latlng) => {
// let context = {
// feature: feature,
// variables: {}
// };
let presasMarker = L.BeautifyIcon.icon({
icon: "presa",
iconSize: [0, 0],
iconAnchor: [7, 10],
iconShape: "circle",
popupAnchor: [0, 0],
innerIconStyle: "font-size:16px;",
borderWidth: 0,
borderColor: "transparent",
backgroundColor: "transparent",
textColor: "#808080"//"#123fb8"
});
return new L.Marker(latlng, {
icon: presasMarker
});
}
});
const style_cuencas = () => {
return {
pane: "pane_cuencas",
opacity: 1,
color: "rgba(0,0,0,0.25)",
dashArray: "",
lineCap: "butt",
lineJoin: "miter",
weight: 1.0,
fill: true,
fillOpacity: 1,
fillColor: "rgba(213,190,245,0.25)"
}
}
let layer_cuencas = new L.geoJson(null, {
attribution: "",
pane: "pane_cuencas",
//onEachFeature: pop_cuencas,
style: style_cuencas
});
const style_limZMs = () => {
return {
pane: "pane_limZMs",
opacity: 1,
color: "rgba(101,43,0,0.6)",
dashArray: "",
lineCap: "butt",
lineJoin: "miter",
weight: 1.0,
fillOpacity: 0
}
}
let layer_limZMs = new L.geoJson(null, {
attribution: "",
pane: "pane_limZMs",
//onEachFeature: pop_limZMs,
style: style_limZMs
});
const style_ZMs = feature => {
switch (String(feature.properties["ZM_NOMBRE"])) {
case "Tuxtla Gutiérrez":
return {
pane: "pane_ZMs",
opacity: 1,
color: "rgba(139,47,0,0.6)",
dashArray: "",
lineCap: "butt",
lineJoin: "miter",
weight: 1.0,
fill: true,
fillOpacity: 1,
fillColor: "rgba(139,60,0,0.6)"
}
//break;
case "Villahermosa":
return {
pane: "pane_ZMs",
opacity: 1,
color: "rgba(139,47,0,0.6)",
dashArray: "",
lineCap: "butt",
lineJoin: "miter",
weight: 1.0,
fill: true,
fillOpacity: 1,
fillColor: "rgba(207,152,62,0.6)"
}
//break;
}
}
let layer_ZMs = new L.geoJson(null, {
attribution: "",
pane: "pane_ZMs",
//onEachFeature: pop_ZMs,
style: style_ZMs
});
const style_ANPs = () => {
return {
pane: "pane_ANPs",
opacity: 1,
color: "#00ff00",
dashArray: "3 2",
lineCap: "butt",
lineJoin: "miter",
weight: 1.0,
fill: true,
fillOpacity: 1,
fillColor:"rgba(0,240,2,0.05)"
}
}
let layer_ANPs = new L.geoJson(null, {
attribution: "",
pane: "pane_ANPs",
//onEachFeature: pop_ANPs,
style: style_ANPs
});
const styleDrawnItems = () => {
let currentId = 0;
drawnItems.eachLayer( l => {
l.feature.properties.id = currentId;
l.feature.properties.color = d3.schemeCategory10[currentId];
l.setStyle({
fillColor: d3.schemeCategory10[currentId],
opacity: 0.5,
fillOpacity: 0.2,
color: d3.schemeCategory10[currentId],
weight: 4
});
currentId++;
});
}
const toggleButtons = () => {
let buttons = [];
buttons.push(document.getElementsByClassName("leaflet-draw-draw-polyline")[0]);
buttons.push(document.getElementsByClassName("leaflet-draw-draw-polygon")[0]);
buttons.push(document.getElementsByClassName("leaflet-draw-draw-rectangle")[0]);
buttons.forEach( b => {
// disable buttons if there are 10 drawn items
if (drawnItems.getLayers().length >= 10) {
b.onClick = "preventEventDefault(); return false";
b.classList.add("draw-control-disabled");
// enable buttons if there are less than 10 drawn items
} else if (drawnItems.getLayers().length < 10) {
b.onClick = null;
b.classList.remove("draw-control-disabled");
}
})
}
const makeBaseMap = () => {
createPane("pane_ANPs", 400);
createPane("pane_munis", 402);
createPane("pane_ZMs", 403);
createPane("pane_limZMs", 404);
createPane("pane_locs", 405);
createPane("pane_cuencas", 406);
createPane("pane_buffer", 407);
createPane("pane_manzanas", 408);
createPane("pane_presas", 409);
createPane("pane_descargas", 410);
createPane("pane_carreteras", 411);
createPane("pane_frontera", 412);
createPane("pane_puerto", 413);
createPane("pane_puentes", 414);
createPane("pane_casetas", 415);
createPane("pane_camellones", 416);
createPane("pane_agebs", 417);
createPane("pane_hoteles", 418);
createPane("pane_hospitales", 419);
createPane("pane_escuelas", 420);
createPane("pane_wbnames",421);
/*$("#basemap-fileLoad").html("Cargando capa 1 de " + baseLayerCounter + "<br>" +
"<span class=\"progress\"><span id=\"baseBar\" class=\"progress-bar progress-bar-striped progress-bar-animated\" role=\"progressbar\" style=\"width: 0%;\" aria-valuenow=\"0\" aria-valuemin=\"0\" aria-valuemax=\"100\"></span>" +
"<span class=\"justify-content-center d-flex position-absolute w-100\">0/" + baseDisplaySize + "</span></span>");*/
layerControl.addOverlay(layer_nombresWB, "Nombres cuerpos de agua");
zip2Lyr("../grijalva/data/escuelas.zip", escuelas, layer_escuelas);
layerControl.addOverlay(layer_escuelas, "Escuelas con 50 personas ocupadas o más<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<i class=\"fa fa-escuela\" style=\"color:#409400;margin-top:3px; margin-left:0px;font-size: 13px;\"></i> Básico<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<i class=\"fa fa-escuela\" style=\"color:#df6400;margin-top:3px; margin-left:0px;font-size: 13px;\"></i> Medio superior<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<i class=\"fa fa-escuela\" style=\"color:#c0d1e5;margin-top:3px; margin-left:0px;font-size: 13px;\"></i> Otras escuelas");
zip2Lyr("../grijalva/data/hospitales.zip", hospitales, layer_hospitales);
layerControl.addOverlay(layer_hospitales, "<span class=\"fa fa-stack\" style=\"padding-top: 6px;\"><i class=\"fas fa-desechos fa-stack-1x\" style=\"color:#ff0000;\"></i><i class=\"fas fa-plus-circle fa-stack-1x fa-inverse\"></i></span>Hospitales con 50 personas ocupadas o más");
zip2Lyr("../grijalva/data/hoteles.zip", hoteles, layer_hoteles);
layerControl.addOverlay(layer_hoteles, "&nbsp; <i class=\"fas fa-bed style=\"color:#808080;\"></i> Hoteles principales");
layerControl.addOverlay(layer_agebswms, "&nbsp; <svg height=\"15\" width=\"15\"><path d=\"M0 0 L15 0 L15 10 L0 10 Z \" x=\"5\" y=\"5\" fill=none stroke=\"#d3d3d3\"></rect></svg><span style=\"font-size=xx-small\">&nbsp;AGEBs 2018</span>");
layerControl.addOverlay(layer_areaVerdeUrbana, "<img src=\"http://gaia.inegi.org.mx/NLB/tunnel/wms/mdm6wms?map=/opt/map/mdm60/mdm61leyendaprueba.map&Request=GetLegendGraphic&format=image/png&Version=1.1.1&Service=WMS&LAYER=c110\" alt=\"Áreas verdes urbanas\" />");
zip2Lyr("../grijalva/data/puentes.zip", puentes, layer_puentes);
layerControl.addOverlay(layer_puentes, "&nbsp; <i class=\"fa fa-puente\" style=\"color:#808080;margin-top:3px; margin-left:0px;font-size: 10px;\"></i> &nbsp;Puentes");
let puerto = omnivore.geojson("../grijalva/data/PuertoFrontera.json", null, layer_puerto);
layerControl.addOverlay(puerto, "&nbsp; <i class=\"fa fa-puertoindustrial\" style=\"margin-top:3px; margin-left:0px;font-size: 12px;\"></i> Puerto");
zip2Lyr("../grijalva/data/PuntosFrontera.zip", puntoFronterizo, layer_frontera);
layerControl.addOverlay(layer_frontera, "&nbsp;<i class=\"fa fa-puntofronterizo\" style=\"color:#808080;margin-top:3px; margin-left:0px;font-size: 16px;\"></i> Punto fronterizo");
layerControl.addOverlay(layer_RedNacionalCaminos, "Red nacional de caminos <br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<img src=\"http://gaia.inegi.org.mx/NLB/tunnel/wms/mdm6wms?map=/opt/map/mdm60/mdm61leyendaprueba.map&Request=GetLegendGraphic&format=image/png&Version=1.1.1&Service=WMS&LAYER=c200\" alt=\"Red Nacional de Caminos\"/>");
zip2Lyr("../grijalva/data/descargas.zip", descargas, layer_descargas);
layerControl.addOverlay(layer_descargas, "&nbsp; <i class=\"fa fa-desechos\" style=\"color:#808080;margin-top:3px;margin-left:0px;font-size: 12px;opacity:0.75;\"></i>&nbsp;&nbsp;Descargas residuales municipales e industriales");
zip2Lyr("../grijalva/data/presas.zip", presas, layer_presas);
layerControl.addOverlay(layer_presas, "&nbsp; <i class=\"fa fa-presa\" style=\"color:#808080;margin-top:3px; margin-left:0px;font-size: 12px;\"></i> Presas principales");
layerControl.addOverlay(layer_manzanaswms, "<img src=\"http://gaia.inegi.org.mx/NLB/tunnel/wms/mdm6wms?map=/opt/map/mdm60/mdm61leyendaprueba.map&Request=GetLegendGraphic&format=image/png&Version=1.1.1&Service=WMS&LAYER=c102m\" alt=\"Manzanas\"/>");
zip2Lyr("../grijalva/data/cuencas.zip", subcuencas, layer_cuencas);
layerControl.addOverlay(layer_cuencas, "&nbsp; <svg height=\"15\" width=\"15\"><path d=\"M0 0 L15 0 L15 10 L0 10 Z \" x=\"5\" y=\"5\" fill=\"rgba(213,190,245,0.5)\" stroke=\"rgba(0,0,0,0.25)\" stroke-dasharray=\" \"></rect></svg> Cuenca y subcuencas del Grijalva, CONAGUA 2017");
layerControl.addOverlay(layer_locUrban, "<img src=\"http://gaia.inegi.org.mx/NLB/tunnel/wms/mdm6wms?map=/opt/map/mdm60/mdm61leyendaprueba.map&Request=GetLegendGraphic&format=image/png&Version=1.1.1&Service=WMS&LAYER=c102\" alt=\"Localidades urbanas\"/>");
zip2Lyr("../grijalva/data/limites_TG_V.zip", tuxtlaVH, layer_limZMs);
layerControl.addOverlay(layer_limZMs, "&nbsp; <svg height=\"15\" width=\"15\"><path d=\"M0 0 L15 0 L15 10 L0 10 Z \" x=\"5\" y=\"5\" fill=\"none\" stroke=\"rgba(101,43,0,0.6)\" stroke-dasharray=\" \"></rect></svg> Límites de zonas metropolitanas");
zip2Lyr("../grijalva/data/limitesZM.zip", zms, layer_ZMs);
layerControl.addOverlay(layer_ZMs, "Zonas metropolitanas<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<svg height=\"15\" width=\"15\"><path d=\"M0 0 L15 0 L15 10 L0 10 Z \" x=\"5\" y=\"5\" fill=\"rgba(139,60,0,0.8)\" stroke=\"rgba(139,47,0,0.6)\" stroke-dasharray=\" \"></rect></svg> Tuxtla Gutiérrez<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<svg height=\"15\" width=\"15\"><path d=\"M0 0 L15 0 L15 10 L0 10 Z \" x=\"5\" y=\"5\" fill=\"rgba(207,152,62,0.8)\" stroke=\"rgba(139,47,0,0.6)\" stroke-dasharray=\" \"></rect></svg> Villahermosa");
layerControl.addOverlay(layer_limiteMunicipal, "<img src=\"http://gaia.inegi.org.mx/NLB/tunnel/wms/wms61?version=1.3.0&service=WMS&request=GetLegendGraphic&sld_version=1.1.0&layer=c101&format=image/png&STYLE=default\" alt=\"Límite municipal\"/>");
zip2Lyr("../grijalva/data/anps.zip", anps, layer_ANPs);
map.addLayer(layer_ANPs);
layerControl.addOverlay(layer_ANPs, "&nbsp; <svg height=\"15\" width=\"15\"><path d=\"M0 0 L15 0 L15 10 L0 10 Z \" x=\"5\" y=\"5\" fill=\"rgba(0,255,0,.3)\" stroke=\"rgba(128,152,72,1.0)\" stroke-dasharray=\"0.5,1\" stroke-dashoffset=\"0.5\"></rect></svg> Áreas Naturales Protegidas 2018");
// Localize Leaflet.Draw texts
L.drawLocal = {
draw: {
toolbar: {
actions: {
title: "Cancelar dibujo",
text: "Cancelar"
},
finish: {
title: "Terminar dibujo",
text: "Terminar"
},
undo: {
title: "Eliminar último punto dibujado",
text: "Eliminar último punto"
},
buttons: {
polyline: "Dibujar una polilínea",
polygon: "Dibujar un polígono",
rectangle: "Dibujar un rectángulo",
circle: "Dibujar un círculo",
marker: "Dibujar un marcador",
circlemarker: "Dibujar un marcador circular"
}
},
handlers: {
circle: {
tooltip: {
start: "Haz click y arrastra para dibujar un círculo"
},
radius: "Radio"
},
circlemarker: {
tooltip: {
start: "Haz click en el mapa para ubicar el marcador circular"
}
},
marker: {
tooltip: {
start: "Haz click en el mapa para ubicar el marcador"
}
},
polygon: {
error: "<strong>Error:</strong>",
tooltip: {
start: "Haz click para empezar a dibujar la figura",
cont: "Haz click para continuar dibujando la figura",
end: "Haz click en el primer punto para cerrar la figura"
}
},
polyline: {
error: "<strong>Error:</strong> las líneas no deben cruzarse",
tooltip: {
start: "Haz click para empezar a dibujar la línea",
cont: "Haz click para continuar dibujando la línea",
end: "Haz click en el último punto para terminar la línea"
}
},
rectangle: {
tooltip: {
start: "Haz click y arrastra para dibujar un rectángulo"
}
},
simpleshape: {
tooltip: {
end: "Suelta el ratón para terminar de dibujar"
}
}
}
},
edit: {
toolbar: {
actions: {
save: {
title: "Guardar los cambios",
text: "Guardar"
},
cancel: {
title: "Cancelar la edición, descarta todos los cambios",
text: "Cancelar"
},
clearAll: {
title: "Limpiar todas las capas",
text: "Limpiar todo"
}
},
buttons: {
edit: "Editar capas",
editDisabled: "No hay capas que editar",
remove: "Eliminar capas",
removeDisabled: "No hay capas que eliminar"
}
},
handlers: {
edit: {
tooltip: {
text: "Arrastra el marcador para editar la figura",
subtext: "Haz click en cancelar para deshacer los cambios"
}
},
remove: {
tooltip: {
text: "Haz click en una figura para eliminarla"
}
}
}
}
}
// leaflet draw control
drawnItems = L.featureGroup().addTo(map);
let drawControl = new L.Control.Draw({
draw: {
polygon: {
showArea: true,
//allowIntersection: false, // Restricts shapes to simple polygons
//drawError: {
//color: "#f9a800",
//message: "<strong>you can't draw that!" // Message that will show when intersect
//},
shapeOptions: {
color: "#42ffc3"
}
},
// disable toolbar item by setting it to false
polyline: {
//allowIntersection:false,
shapeOptions: {
color: "#42ffc3"
}
},
rectangle: {
shapeOptions: {
color: "#42ffc3"
}
},
marker: false,
circlemarker: false,
circle: false // Turns off this drawing tool
},
edit: {
featureGroup: drawnItems,
//remove: false
}
});
map.addControl(drawControl);
// set z-index of panes so actions can be done on drawnItems when editing/deleting
map.on(L.Draw.Event.CREATED, event => {
$("#mexmap .leaflet-pane.leaflet-overlay-pane").css("z-index", "400");
let layer = event.layer,
feature = layer.feature = layer.feature || {}; // Initialize feature
feature.type = feature.type || "Feature"; // Initialize feature.type
feature.properties = feature.properties || {}; // Initialize feature.properties
drawnItems.addLayer(layer); // whatever you want to do with the created layer
// give each drawn item an id and color from an array
styleDrawnItems();
// disable buttons if there are 10 drawn items
toggleButtons();
// after drawing shape, update chart with data for polygons contained inside it
updateCharts();
});
map.on(L.Draw.Event.DELETESTART, () => {
// before deleting shape, bring pane up
$("#mexmap .leaflet-pane.leaflet-overlay-pane").css("z-index", "451");
});
map.on(L.Draw.Event.DELETED, () => {
// after deleting a drawn item, refresh the list of ids and colors
styleDrawnItems();
// enable buttons if there are less than 10 drawn items
toggleButtons();
// after deleting shape, send pane down
$("#mexmap .leaflet-pane.leaflet-overlay-pane").css("z-index", "400");
// and update chart with data:
// if there are drawnItems, use them. If not, use original data
updateCharts();
});
map.on(L.Draw.Event.EDITSTART, () => {
$("#mexmap .leaflet-pane.leaflet-overlay-pane").css("z-index", "451");
// before editing shape, bring pane up
});
map.on(L.Draw.Event.EDITSTOP, () => {
$("#mexmap .leaflet-pane.leaflet-overlay-pane").css("z-index", "400");
// before editing shape, bring pane up
});
map.on(L.Draw.Event.EDITED, () => {
// before editing shape, send pane down
$("#mexmap .leaflet-pane.leaflet-overlay-pane").css("z-index", "400");
// after editing shape, update chart with data for polygons contained inside it
updateCharts();
});
}
\ No newline at end of file
/*
* Copyright 2019 - All rights reserved.
* Rodrigo Tapia-McClung
*
* August-September 2019
*/
/* globals map, userFiles, userDates, indicators, indicatorVars, indicatorsNames */
/* exported makeIndicatorGraph, getData, updateData, getDataInSelection */
let minDate, maxDate, xLine, yLine;
const sortByDateAscending = (a, b) => {
// Dates will be cast to numbers automagically:
return a.date - b.date;
}
//TODO: scale data to appropriate units? m2-> ha? m -> km?
const getData = async () => {
let queryColumns = ["sum(areacpo) as area", "sum(perimcpo) as perimeter", "avg(dlccpo) as costa", "avg(dimfrcpo) as df"],
timeParse = d3.timeParse("cuerpos_%B_%Y"),
data = {};
indicators.map( indicator => {
data[indicator] = [];
data[indicator].push({name: `${indicator}0`, values: []});
});
const baseUrl = new URL(`/data`, window.location.href).href;
const userFilePromises = userFiles.map( async monthYear => {
let queryDB = `${baseUrl}/query/${monthYear}?columns=${queryColumns.join(",")}`;
const dbData = await d3.json(queryDB);
return {date: timeParse(monthYear), value: dbData[0]};
});
const chartData = await Promise.all(userFilePromises);
chartData.map( monthData => {
indicators.map( indicator => {
data[indicator][0].values.push({
"date": monthData.date,
"value": +monthData.value[indicator]
});
// order data[i].values.date
data[indicator].forEach(d => d.values.sort(sortByDateAscending));
});
});
return data;
}
const getDataInSelection = async () => {
let geojson = drawnItems.toGeoJSON(),
queryColumns = ["sum(area) as area", "sum(perimeter) as perimeter", "avg(costa) as costa", "avg(df) as df"],
timeParse = d3.timeParse("%B_%Y"),
data = {};
indicators.map( indicator => {
data[indicator] = [];
});
const baseUrl = new URL(`/data`, window.location.href).href;
// Convert drawn items to GeoJSON and check what's inside each one of them
const drawnItemPromises = drawnItems.toGeoJSON().features.map( async (item, i) => {
// set SRID fro drawn features
geojson.features[i].geometry.crs = {"type":"name","properties":{"name":"EPSG:4326"}};
indicators.map( async indicator => {
data[indicator].push({name: indicator + (i + 1), values: []});
});
// End up with data = {area: [{name: "area1", values: []}], perimeter: [{name: "perimeter1", values: []}]...}
let filter = `ST_Intersects(geom, (SELECT ST_Multi(ST_GeomFromGeoJSON('${JSON.stringify(geojson.features[i].geometry)}') ) ) )`
const userFilePromises = userFiles.map(async monthYear => {
let queryData = `${baseUrl}/query/${monthYear}?columns=${queryColumns.join(",")}&filter=${filter}&group=1%3D1`;
const selectionQuery = await d3.json(queryData);
return {date: timeParse(monthYear), value: selectionQuery[0]};
});
const selectionData = await Promise.all(userFilePromises);
selectionData.map( itemData => {
indicators.map( indicator => {
let itemDataValue = itemData.value != undefined ? itemData.value[indicator] : 0;
data[indicator][i].values.push({date: itemData.date, value: itemDataValue });
});
});
});
const drawnItemsData = await Promise.all(drawnItemPromises);
return data;
}
const summarizeData = (allData, indicator) => {
let dataFull = allData;
let data = dataFull[indicator];
let data2 = [];
data.map( a => data2.push(a.values)); // flatten array of data[i].values
data2 = [].concat.apply([], data2);
// insert sum of each date to first element of data array with name "column0"
data.unshift({
"name": `${indicator}0`,
"values": d3.nest()
.key( d => d.date)
// return d3.sum or d3.average depending on indicator
.rollup( v => { return indicator == "costa" || indicator == "df" ? d3.mean(v, v => v.value) : d3.sum(v, v => v.value) })
.entries(data2)
// map "key" and "value" from d3.nest().rollup() to date and value
.map( group => {
return {
date: new Date(group.key),
value: group.value
}
})
});
// order data[i].values.date
data.forEach(d => d.values.sort(sortByDateAscending));
return data;
}
const makeIndicatorGraph = () => {
let data = [],
width = 450,
height = 400,
padding = 30,
margin = {top: 5, right: 10, bottom: 100, left: 35},
id, // variable in data to use as identifier
lineVariables = null, // list of variables to display as lines
displayName, // variable in data to use as x axis labels
transitionTime = 500,
//lineColor = "#17678E",
//pointColor = "#17678E",
defaultLineColor = "CadetBlue",
defaultPointColor = "CadetBlue",
lineColors = d3.scaleOrdinal(d3.schemeCategory10),
pointColors = d3.scaleOrdinal(d3.schemeCategory10),
lineAxisLabel = "",
xAxisFormat = d3.timeFormat("%b '%y"), // mm 'yy
yAxisFormat = d3.format(".2s"), // SI format with 2 significant numbers
//rightAxisFormat = ".0%", // rounded percentage
legend = false,
title = "",
tooltipUnits = "",
tooltipFormat = "",
//legend = {title: "title", translateX: 100, translateY: 0, items:["item1","item2"]}
legendContainer = "legendZone",
updateData;
const chart = selection => {
// get data ranges using values from displayName
minDate = d3.min(data[0].values, d => {
return d[displayName];
});
minDate = d3.timeDay.offset(minDate, -15) // get previous month to get correct x-axis alignment
maxDate = d3.max(data[0], d => {
return d[displayName];
});
maxDate = d3.timeDay.offset(maxDate, 15) // get next month to get correct x-axis alignment
selection.each( () => {
// add graph svg to selected container on webpage
let areaSVG = selection
.append("svg")
// don't set height/width but use viewBox so charts get resized
// if they are within a container that can be resized (also scales text)
.attr("viewBox", `0 0 ${width} ${height}`)
//.attr("preserveAspectRatio", "xMinYMin meet"),
.attr("preserveAspectRatio", "xMidYMid meet"),
g = areaSVG.append("g")
.attr("class", "lineChart")
.attr("transform", `translate(${margin.left}, ${margin.top})`);
// add x axis
g.append("g")
.attr("class", "x axis")
.attr("transform", `translate(30, ${height - margin.left})`);
// add x axis for ticks
g.append("g")
.attr("class", "x axis-ticks")
.attr("transform", `translate(30, ${height - margin.left})`);
// add x axis for month labels
g.append("g")
.attr("class", "x axis-month-labels")
.attr("transform", `translate(30, ${height - margin.left})`);
// add x axis for year labels
g.append("g")
.attr("class", "x axis-year-labels")
.attr("transform", `translate(30, ${height - margin.left + 12})`);
//add left y axis
g.append("g")
.attr("class", "y axis")
.attr("transform", "translate(30,0)");
//label y axes
areaSVG.append("text")
.attr("class", "label")
.attr("text-anchor", "middle") // this makes it easy to centre the text as the transform is applied to the anchor
.attr("transform", `translate(${padding / 3}, ${height / 2}) rotate(-90)`) // text is drawn off the screen top left, move down and out and rotate
.attr("fill", "#b2b2b2");
//.text("Superficie inundada");
// append line container
g.append("g")
.attr("class", "lines")
.attr("transform", "translate(30, 0)");
// append circle container
g.append("g")
.attr("class", "circles")
.attr("transform", "translate(30, 0)");
// add chart title container
areaSVG.append("text")
.attr("class", "title label")
.attr("text-anchor", "middle") // this makes it easy to centre the text as the transform is applied to the anchor
.attr("transform", `translate(${width / 2}, ${margin.top * 4})`); // text is drawn off the screen top left, move down and out and rotate
// add the tooltip area to the webpage
let tooltip;
if (d3.select("div.tooltip").empty()) { // check if no tooltip div exists and add if necessary
tooltip = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
}
// define axes with ranges
xLine = d3.scaleTime().range([0, width - margin.left - margin.right - 30]).clamp(true);
yLine = d3.scaleLinear().range([height - margin.left - margin.right, 0 + 35])
// scale data range
xLine.domain([minDate, maxDate]).nice();
//yLine.domain(d3.extent(data, function(d) { return d[lineVariables[0]]; })).nice();
//yLine.domain([0, d3.max(data, function(d) { return d[lineVariables[0]]; })]).nice();
// Calculate min and max values of data to define chart y-axis domain
//let minDomain = d3.min(data.map(d => d.values).flat().map(d => d.value));
//console.log("data: ", data.map(d => d.values));
let maxDomain = d3.max(data.map(d => d.values).flat().map(d => d.value));
yLine.domain([0, maxDomain]).nice();
//yLine.domain([0, d3.max(data[0].values, d => d.value)]).nice();
// add axes
let xaxis = g.select(".x.axis");
xaxis.call(
d3.axisBottom(xLine)
// .ticks(d3.timeMonth.every(1))
// .tickFormat(d3.timeFormat("%b '%y"))
)
.selectAll("text")
//.attr("y", 0)
//.attr("x", 9)
.attr("y", 15)
.attr("x", 0)
.attr("dy", ".35em")
.attr("font-size", "12px")
//.attr("transform", "rotate(90)")
//.style("text-anchor", "start");
.style("text-anchor", "middle")
.style("fill", "#b2b2b2");
// format x-axis lines for minor and major ticks
//TODO: check this line to separate years starting in Jan. Maybe it is not needed
xaxis.selectAll("g.tick line")
.attr("y2", d => { // d is the tick value
if (d3.timeFormat("%m")(d) == "01") //if it is Jan
return -height - 6;
else
return 6;
})
.attr("transform", d => { // d is the tick value
if (d3.timeFormat("%m")(d) == "01") //if it is Jan
return "translate(0,6)";
})
.attr("class", d => { // d is the tick value
if (d3.timeFormat("%m")(d) == "01") //if it is Jan
return "yearTick";
});
let yaxis = g.select(".y.axis");
yaxis.transition()
.duration(500)
.call(d3.axisLeft(yLine)
/*.tickFormat(d3.format(".2s"))*/
);
// color axis labels
g.selectAll(".y.axis g.tick text")
.style("fill", "#b2b2b2"); // function (d,i) {return (i%2)?"red":"blue";})
let title = selection.select(".title.label");
title.text(chart.title())
.style("fill", "#b2b2b2");
selection.select(".title.label")
.append("tspan")
.attr("class", "far")
.html(" \uf059")
//.style("color", "#b2b2b2")
.style("cursor", "pointer")
.on("click", () => { // when clicking ? display modal with information about indicator
$("#explainIndicatorModal").on("show.bs.modal", () => {
let indicatorName = indicatorsNames[indicators.indexOf(id)]
$("#explainIndicatorModal").find("#explainIndicatorModalTitle").text(indicatorName);
$("#explainIndicatorModal").find(".modal-body").html(indicatorVars[id].explanation);
});
$("#explainIndicatorModal").modal();
});
// update chart
updateData = () => {
let time = map.timeDimension.getCurrentTime();
// update data ranges using values from displayName and lineVariables
minDate = d3.min(data[0].values, d => {
return d[displayName];
});
minDate = d3.timeDay.offset(minDate, -15) // get previous month to get correct x-axis alignment
maxDate = d3.max(data[0].values, d => {
return d[displayName];
});
maxDate = d3.timeDay.offset(maxDate, 15) // get next month to get correct x-axis alignment
// update axes' domain
xLine.domain([minDate, maxDate])
//.nice()
//.nice(d3.timeMonth)
.range([0, width - margin.left - margin.right - 30])
.clamp(true);
//yLine.domain(d3.extent(data, function(d) { return d[lineVariables[0]]; }))
//yLine.domain([0, d3.max(data, function(d) { return d[lineVariables[0]]; })]).nice()
// Calculate min and max values of data to define chart y-axis domain
//let minDomain = d3.min(data.map(d => d.values).flat().map(d => d.value));
let maxDomain = d3.max(data.map(d => d.values).flat().map(d => d.value));
yLine.domain([0, maxDomain])
//yLine.domain([0, d3.max(data[0].values, d => d.value)]).nice()
.range([height - margin.left - margin.right, 0 + 35])
.nice();
// filter data to only use the ones needed for current time
let filteredData = [];
data.forEach((d, i) => {
filteredData.push({
name: d.name,
values: []
});
d.values.filter(v => {
if (v.date <= new Date(time)) {
filteredData[i].values.push(v);
}
});
});
// update axes
let g = selection.select(".lineChart");
// update axis line with no ticks
let xaxis = g.select(".x.axis");
xaxis.transition()
.duration(500)
.call(
d3.axisBottom(xLine)
.ticks(0)
.tickSize(0)
);
// update axis ticks - one for each month
let xaxisTicks = g.select(".x.axis-ticks");
let monthsInGraph = [];
userDates.forEach(d => {
monthsInGraph.push(new Date(d));
});
xaxisTicks.transition()
.duration(500)
.call(
d3.axisBottom(xLine)
.tickValues(monthsInGraph)
.tickFormat("")
);
// update axis month labels depending on how many months there are
let xaxisLabels = g.select(".x.axis-month-labels");
// FIXME: check how this displays ticks when there are many months/years
// this fix gives up to 10 ticks
// maybe need to set up more conditions
let numberOfTicks = monthsInGraph.length < 6 ? d3.timeMonth.every(1) : 6;
xaxisLabels.transition().duration(500)
.call(
d3.axisBottom(xLine)
.ticks(numberOfTicks)
.tickFormat(d3.timeFormat("%b"))
)
.selectAll("text")
.style("fill", "#b2b2b2")
.attr("font-size", "12px");
// update axis year labels depending on how many months there are
let xaxisYearLabels = g.select(".x.axis-year-labels");
xaxisYearLabels.transition().duration(500)
.call(
d3.axisBottom(xLine)
.ticks(numberOfTicks)
.tickFormat(d3.timeFormat("'%y"))
)
.selectAll("text")
.style("fill", "#b2b2b2")
.attr("font-size", "12px");
// format x-axis lines for minor and major ticks
//TODO: check this line to separate years starting in Jan. Maybe it is not needed
xaxis.selectAll("g.tick line")
.attr("y2", d => { // d is the tick value
if (d3.timeFormat("%m")(d) == "01") //if it is Jan
return -height - 6;
else
return 6;
})
.attr("transform", d => { // d is the tick value
if (d3.timeFormat("%m")(d) == "01") //if it is Jan
return "translate(0,6)";
})
.attr("class", d => { // d is the tick value
if (d3.timeFormat("%m")(d) == "01") //if it is Jan
return "yearTick";
});
let yaxis = g.select(".y.axis");
yaxis.transition()
.duration(500)
.call(d3.axisLeft(yLine)
.tickFormat(yAxisFormat));
// color axis labels
g.selectAll(".y.axis g.tick text")
.style("fill", "#b2b2b2");
// define line
let line = d3.line()
.defined(d => d)
.curve(d3.curveMonotoneX)
.x(d => xLine(d.date))
.y(d => yLine(d.value));
// get lines
let lines = g.select(".lines");
lines.selectAll("path.line")
.data(filteredData)
.join("path") // handle enter, update, and exit automagically
.attr("stroke", (d, i) => {
return i == 0 ? defaultLineColor : lineColors(i - 1);
})
.attr("class", "line")
.attr("fill", "none")
.attr("stroke-linejoin", "round")
.attr("stroke-linecap", "round")
.attr("stroke-width", 1.5)
.attr("d", d => line(d.values))
// get circles
let circles = g.select(".circles"),
tooltip = d3.select(".tooltip");
//tooltipFormat = d3.timeFormat("%b "%y"),
//tooltipFormat = d3.format(",.2f")
//tooltipFormat = d3.format(",.3s");
circles.selectAll("g") // using nested data, so need to make a g to contain stuff
.data(filteredData)
.join( // handle enter, update, and exit separately for overall g circle containers
enter => enter
.append("g")
.attr("class", d => d.name)
.attr("stroke", (d, i) => {
return i == 0 ? defaultPointColor : pointColors(i - 1);
})
.attr("fill", (d, i) => {
return i == 0 ? defaultPointColor : pointColors(i - 1);
}),
update => update
.attr("class", d => d.name)
.attr("stroke", (d, i) => {
return i == 0 ? defaultPointColor : pointColors(i - 1);
})
.attr("fill", (d, i) => {
return i == 0 ? defaultPointColor : pointColors(i - 1);
}),
exit => exit.remove()
)
// nested join
.selectAll("circle").data(d => d.values) // access internal values array for circles here
.join( // handle enter, update, and exit separately for circles
enter => enter
.append("circle")
.attr("class", "circle")
.attr("r", 2.5)
.attr("cx", d => xLine(d.date))
.attr("cy", d => yLine(d.value))
.on("mouseover", d => {
tooltip.html(`${tooltipFormat(d.value)} ${tooltipUnits}`);
let tpWidth = tooltip.node().offsetWidth; // to center tooltip
tooltip.style("left", `${d3.event.pageX - tpWidth / 2}px`)
.style("top", `${d3.event.pageY - 23}px`)
.style("font-family", "Consolas, courier")
.style("font-size", "10pt")
.transition()
.duration(200)
.style("opacity", .9);
})
.on("mouseout", () => {
tooltip.transition()
.duration(500)
.style("opacity", 0);
}),
update => update
.attr("cx", d => xLine(d.date))
.attr("cy", d => yLine(d.value)),
exit => exit.remove()
);
}
});
}
chart.width = function(value) {
if (!arguments.length) return width;
width = value;
return chart;
};
chart.height = function(value) {
if (!arguments.length) return height;
height = value;
return chart;
};
chart.margin = function(value) {
if (!arguments.length) return margin;
margin = value;
return chart;
};
chart.id = function(value) {
if (!arguments.length) return id;
id = value;
return chart;
};
chart.lineVariables = function(value) {
if (!arguments.length) return lineVariables;
lineVariables = value;
return chart;
};
chart.displayName = function(value) {
if (!arguments.length) return displayName;
displayName = value;
return chart;
};
chart.transitionTime = function(value) {
if (!arguments.length) return transitionTime;
transitionTime = value;
return chart;
};
chart.legend = function(value) {
if (!arguments.length) return legend;
legend = value;
return chart;
};
chart.legendContainer = function(value) {
if (!arguments.length) return legendContainer;
legendContainer = value;
return chart;
};
chart.data = function(value) {
if (!arguments.length) return data;
data = value;
if (typeof updateData === "function") updateData();
return chart;
};
chart.lineColors = function(value) {
if (!arguments.length) return lineColors;
lineColors = value;
return chart;
};
chart.pointColors = function(value) {
if (!arguments.length) return pointColors;
pointColors = value;
return chart;
};
chart.xAxisFormat = function(value) {
if (!arguments.length) return xAxisFormat;
xAxisFormat = value;
return chart;
};
chart.yAxisFormat = function(value) {
if (!arguments.length) return yAxisFormat;
yAxisFormat = value;
return chart;
};
chart.transitionTime = function(value) {
if (!arguments.length) return transitionTime;
transitionTime = value;
return chart;
};
chart.lineAxisLabel = function(value) {
if (!arguments.length) return lineAxisLabel;
lineAxisLabel = value;
return chart;
};
chart.title = function(value) {
if (!arguments.length) return title;
title = value;
return chart;
};
chart.tooltipUnits = function(value) {
if (!arguments.length) return tooltipUnits;
tooltipUnits = value;
return chart;
};
chart.tooltipFormat = function(value) {
if (!arguments.length) return tooltipFormat;
tooltipFormat = value;
return chart;
};
return chart;
}
/*
* Copyright 2019 - All rights reserved.
* Rodrigo Tapia-McClung
*
* December 2020
*/
/* globals omnivore, Promise, chroma, makeBaseMap, makeIndicatorGraph, getData, getDataInSelection */
/* exported indicators. userFiles, userDates, timeParse, layerControl, updateCharts */
let timeParse,
timeFormat,
timeDimensionControl,
userFiles = [],
monthArray = ["Enero", "Febrero", "Marzo", "Abril", "Mayo", "Junio", "Julio", "Agosto", "Septiembre", "Octubre", "Noviembre", "Diciembre"],
dateArray = [],
dateMin,
dateMax,
minUserDate,
maxUserDate,
userDates,
map,
overlay,
glmap,
osmLayer, cartoLightLayer, cartoDarkLayer,
timeLayer,
layerControl,
scale;
// define empty objects and indicators
let maxIndicators = {},
minIndicators = {},
indicators = ["areacpo", "perimcpo", "dlccpo", "dimfrcpo"],
indicatorsNames = ["Área", "Perímetro", "Desarrollo de la línea de costa", "Dimensión fractal"],
indicatorsUnits = ["m\u00B2", "m", "", ""],
indicatorsxAxisFormat = [".2s", ".2s", ".2f", ".2f"],
indicatorVars = {},
cols = [];
let colors = {
"Agua permanente": {
"fill": "#00c5ff"
},
"Áreas temporalmente inundadas": {
"fill": "#ff5500"
},
"Suelos húmedos-vegetación acuática": {
"fill": "#98e600"
},
"Capa base G3WBM": {
"fill": "#aaaaaa"
}
}
// Add options to combo box, and set their chart variables
// chart containers must already exist in index.php
indicators.forEach( (indicator, index) => {
// colnames for queries
cols.push(`min(${indicator}) as min${indicator}`);
cols.push(`max(${indicator}) as max${indicator}`);
// initialize min and max objects to hold values for each indicator and add select options
maxIndicators[indicator] = 0;
minIndicators[indicator] = 1e30;
$("#indicatorSelect").append(`<option value="${indicator}"> ${indicatorsNames[index]}</option>`);
// chart variables
indicatorVars[indicator] = {
"chart": `${indicator}Chart`,
"chartaData": `${indicator}Data`,
"container": `#${indicator}-graph`
};
});
indicatorVars.areacpo.explanation = "Muestra la suma del &aacute;rea contenida en los pol&iacute;gonos dentro de la selecci&oacute;n o en toda la regi&oacute;n de estudio.";
indicatorVars.perimcpo.explanation = "Muestra la suma del per&iacute;metro de los pol&iacute;gonos dentro de la selecci&oacute;n o en toda la regi&oacute;n de estudio.";
indicatorVars.dlccpo.explanation = "Relaciona el per&iacute;metro de un cuerpo de agua o longitud de l&iacute;nea de costa con el per&iacute;metro de un c&iacute;rculo de igual \
&aacute;rea (A) que el cuerpo. El valor m&iacute;nimo es 1, el cual corresponde a un c&iacute;rculo perfecto, mientras que el valor m&aacute;ximo no tiene l&iacute;mite. \
En varios estudios se han obtenido valores mayores de 20, como en algunos lagos en Finlandia. Los valores cercanos a 1 son indicativos de formas circulares, mientras que \
los valores mayores de 3 son referencia de cuerpos de agua con contornos de formas alargadas. \
<br/><br/> \
Se calcula como \
<p class=\"equation\">DI = <span class=\"frac\"><sup>P</sup><span>&frasl;</span><sub>2&radic;<span style=\"text-decoration: overline;\">&pi; A</span></sub></span>,</p> \
donde P es el per&iacute;metro y A es el &aacute;rea.";
indicatorVars.dimfrcpo.explanation = "La geometr&iacute;a fractal se usa para hacer referencia a objetos demasiado irregulares y que tienen la propiedad de ser invariantes ante \
cambios de escala. En el caso de la hidrolog&iacute;a, este &iacute;ndice nos refiere la sinuosidad del contorno de un cuerpo de agua. La dimensi&oacute;n presenta valores \
reales no negativos entre 1 y 2. Los valores de este &iacute;ndice aumentan conforme el contorno del cuerpo de agua es más sinuoso. El an&aacute;lisis fractal se ha utilizado \
con &eacute;xito para medir y caracterizar rasgos lineales irregulares como las costas. \
<br/></br> \
Se calcula como \
<p class=\"equation\">DF = <span class=\"frac\"><sup>2 ln(P/4)</sup><span>&frasl;</span><sub>ln(A)</sub></span>,</p> \
donde P es el per&iacute;metro y A es el &aacute;rea.";
let currentTiles = {},
allTiles = {};
d3.json("https://unpkg.com/d3-time-format@2.1.1/locale/es-MX.json").then(locale => {
d3.timeFormatDefaultLocale(locale);
timeParse = d3.timeParse("%B_%Y");
timeFormat = d3.timeFormat("cuerpos_%B_%Y");
setupTimeDimensionControl();
setupDates()
.then(dates => populateDates(dates))
.then(userData => setupMap(userData))
.then(map => populateMap(map));
});
const sortInitialDateAscending = (a, b) => {
// Dates will be cast to numbers automagically:
return a - b;
}
// query available dates on DB
const setupDates = () => {
return new Promise( resolve => {
const baseUrl = new URL(`/data`, window.location.href).href;
let layersQuery = `${baseUrl}/list_layers`;
d3.json(layersQuery).then(layers => {
layers.forEach(layer => {
if (layer.f_table_name.startsWith("cuerpos")) {
let table = layer.f_table_name.split("cuerpos_")[1];
dateArray.push(timeParse(table)); // convert filenames to dates
}
})
dateArray = dateArray.sort(sortInitialDateAscending); // order dates
dateMin = d3.min(dateArray);
dateMax = d3.max(dateArray);
//userFiles = dateArray.map( month => timeFormat(month)); // order table names by date
const dates = {min:dateMin, max:dateMax, dates:dateArray};
resolve(dates);
});
});
}
const populateDates = (dates) => { // fill out date pickers with available dates
return new Promise( resolve => {
$.datepicker.regional["es"] = {
closeText: "Cerrar",
prevText: "&#x3c;Ant",
nextText: "Sig&#x3e;",
currentText: "Hoy",
monthNames: ["Enero", "Febrero", "Marzo", "Abril", "Mayo", "Junio",
"Julio", "Agosto", "Septiembre", "Octubre", "Noviembre", "Diciembre"
],
monthNamesShort: ["Ene", "Feb", "Mar", "Abr", "May", "Jun",
"Jul", "Ago", "Sep", "Oct", "Nov", "Dic"
],
dayNames: ["Domingo", "Lunes", "Martes", "Mi&eacute;rcoles", "Jueves", "Viernes", "S&aacute;bado"],
dayNamesShort: ["Dom", "Lun", "Mar", "Mi&eacute;", "Juv", "Vie", "S&aacute;b"],
dayNamesMin: ["Do", "Lu", "Ma", "Mi", "Ju", "Vi", "S&aacute;"],
weekHeader: "Sm",
dateFormat: "yy/mm/dd",
firstDay: 1,
isRTL: false,
showMonthAfterYear: false,
yearSuffix: ""
}
$.datepicker.setDefaults($.datepicker.regional["es"]);
// month pickers
$("#date-initial").datepicker({
minDate: dates.min,
maxDate: dates.max,
defaultDate: dates.min,
changeMonth: true,
changeYear: true,
showButtonPanel: true,
dateFormat: "M yy",
onClose: function() {
let month = $("#ui-datepicker-div .ui-datepicker-month :selected").val();
let year = $("#ui-datepicker-div .ui-datepicker-year :selected").val();
minUserDate = new Date(year, month, 1); // initial date
$(this).datepicker("setDate", minUserDate);
$("#date-final").datepicker("option", {
"minDate": minUserDate,
disabled: false
});
// hack to avoid needing to change date twice in second datepicker
setTimeout(() => { $("#date-final").datepicker("show") }, 10);
},
beforeShow: el => {
$("#ui-datepicker-div").toggleClass("hide-calendar", $(el).is("[data-calendar=\"false\"]"));
}
});
$("#date-final").datepicker({
maxDate: dates.max,
defaultDate: dates.max,
changeMonth: true,
changeYear: true,
showButtonPanel: true,
disabled: true,
dateFormat: "M yy",
onClose: function() {
let month = $("#ui-datepicker-div .ui-datepicker-month :selected").val();
let year = $("#ui-datepicker-div .ui-datepicker-year :selected").val();
maxUserDate = new Date(year, month, 1); // final date
$(this).datepicker("setDate", maxUserDate);
// use .setUTCHours(6,0,0) to adjust DST offset fror some months
let startUserDate = new Date(minUserDate.setUTCHours(6,0,0));
let endUserDate = new Date(maxUserDate.setUTCHours(6,0,0));
// pass new timeinterval to timeDimension player
userDates = L.TimeDimension.Util.explodeTimeRange(startUserDate, endUserDate, "P1M");
userFiles = userDates.map( month => timeFormat(month)); // order table names by date
// change date selectors from main screen to map
$("#date-initial").detach().appendTo("#datePickers");
$("#date-final").detach().appendTo("#datePickers");
$("#datePickers").css("display", "block");
// hide initial screen and show map
$("#startHeader").remove();
$("#initial-backdrop").remove();
$("#mainContainer")[0].style.setProperty("display", "flex", "important")
$("#mexmap").show();
// When closing final-date, either setup or update map
if (!map) {
resolve({min: startUserDate, max: endUserDate});
} else {
// FIXME: no need to pass data here?
updateMap({map: map, min: startUserDate, max: endUserDate});
}
},
beforeShow: (el, inst) => {
inst.input.datepicker("refresh");
$("#ui-datepicker-div").toggleClass("hide-calendar", $(el).is("[data-calendar=\"false\"]"));
}
});
})
}
const setupMap = (dates) => {
return new Promise( resolve => {
// make body tag to have style height: 100%
$("body").css("height", "100%");
osmLayer = L.tileLayer("http://{s}.tile.osm.org/{z}/{x}/{y}.png", {
maxZoom: 19,
attribution: "&copy; <a href=\"http://osm.org/copyright\" target=\"_blank\">OpenStreetMap</a> contributors"
});
cartoLightLayer = L.tileLayer("https://cartodb-basemaps-{s}.global.ssl.fastly.net/light_all/{z}/{x}/{y}.png", {
maxZoom: 19,
attribution: "Map tiles by Carto, under CC BY 3.0. Data by OpenStreetMap, under ODbL."
});
cartoDarkLayer = L.tileLayer("https://cartodb-basemaps-{s}.global.ssl.fastly.net/dark_all/{z}/{x}/{y}.png", {
maxZoom: 19,
attribution: "Map tiles by Carto, under CC BY 3.0. Data by OpenStreetMap, under ODbL."
});
//let bounds = cuencaBufferMask.getBounds();
let southWest = L.latLng(15.08, -94.42),
northEast = L.latLng(18.82, -91.52),
bounds = L.latLngBounds(southWest, northEast);
map = L.map("mexmap", {
//center: [17.22, -92.28],
minZoom: 7, //8
maxZoom: 18,
zoom: 7, //6
attributionControl: false,
timeDimension: true,
timeDimensionOptions: {
times: userDates,
currentTime: dates.min
},
maxBounds: bounds
}).setView([16.96, -92.97], 8);
cartoDarkLayer.addTo(map);
// set sync map on right
let osmLayerOverlay = L.tileLayer("http://{s}.tile.osm.org/{z}/{x}/{y}.png", {
maxZoom: 19,
attribution: "&copy; <a href=\"http://osm.org/copyright\" target=\"_blank\">OpenStreetMap</a> contributors"
}),
cartoLightLayerOverlay = L.tileLayer("https://cartodb-basemaps-{s}.global.ssl.fastly.net/light_all/{z}/{x}/{y}.png", {
maxZoom: 19,
attribution: "Map tiles by Carto, under CC BY 3.0. Data by OpenStreetMap, under ODbL."
}),
cartoDarkLayerOverlay = L.tileLayer("https://cartodb-basemaps-{s}.global.ssl.fastly.net/dark_all/{z}/{x}/{y}.png", {
maxZoom: 19,
attribution: "Map tiles by Carto, under CC BY 3.0. Data by OpenStreetMap, under ODbL."
});
overlay = L.map("overlaydiv", {
zoomControl: false,
inertia: false,
keyboard: false,
//dragging: false,
scrollWheelZoom: true,
attributionControl: false,
zoomAnimation: true
}).setView([16.96, -92.97], 8);
cartoDarkLayerOverlay.addTo(overlay);
L.control.attribution({position: "bottomright"}).addTo(overlay);
// on base layer on main map, change base layer on overlay map
map.on("baselayerchange", event => {
overlay.eachLayer(l => overlay.removeLayer(l));
if (event.name == "Carto Dark") {
cartoDarkLayerOverlay.addTo(overlay);
} else if (event.name == "Carto Light") {
cartoLightLayerOverlay.addTo(overlay);
} else if (event.name == "OpenStreetMap") {
osmLayerOverlay.addTo(overlay);
}
});
const offsetGlobal = (center, zoom, refMap, tgtMap) => {
var refC = refMap.getContainer();
var tgtC = tgtMap.getContainer();
var pt = refMap.project(center, zoom)
.subtract([refC.offsetLeft, refC.offsetTop])
.subtract(refMap.getSize().divideBy(2))
.add([tgtC.offsetLeft, tgtC.offsetTop])
.add(tgtMap.getSize().divideBy(2));
return refMap.unproject(pt, zoom);
}
map.sync(overlay, {offsetFn: offsetGlobal});
//console.log(userFiles);
// query db to get min/max values per month and indicator and store them in an object
queryFiles().then( minmax => {
minmax.map( minmaxMonth => {
indicators.forEach( (indicator) => {
minIndicators[indicator] = Math.min(minIndicators[indicator], minmaxMonth[`min${indicator}`]);
maxIndicators[indicator] = Math.max(maxIndicators[indicator], minmaxMonth[`max${indicator}`]);
});
});
resolve({"map": map, "minIndicators": minIndicators, "maxIndicators": maxIndicators});
});
});
}
const flatten = arrays => {
return [].concat(arrays);
}
const queryFiles = () => {
return Promise.all(userFiles.map(getMinMax))
// the result is an array of arrays, so we'll flatten them here
.then(flatten);
}
const getMinMax = table => {
return new Promise( resolve => {
const baseUrl = new URL(`/data`, window.location.href).href;
let minmaxQuery = `${baseUrl}/query/${table}?columns=${cols.join(", ")}`;
d3.json(minmaxQuery).then( minmax => {
resolve(minmax[0]);
});
});
}
const populateMap = async (mapData) => {
const chartData = await getData();
// Define charts with reusable components
indicators.map( async (indicator, index) => {
// indicatorVars[indicator].chart gives areaChart, perimeterChart, etc.
// First, make all charts with same general options
indicatorVars[indicator].chart = makeIndicatorGraph()
.width(Math.floor($(indicatorVars[indicator].container)[0].offsetParent.offsetWidth * 0.95))
.height(Math.floor($(indicatorVars[indicator].container)[0].offsetParent.offsetHeight * 0.95))
.lineVariables([`${indicator}0`]) // d.area is used to draw stuff on y axis
.displayName("date") // d.date is used to draw stuff on x axis
.lineAxisLabel("(m)")
.id(indicator)
.xAxisFormat(d3.timeFormat("%b '%y")) // label x axis as mm 'yy
.yAxisFormat(d3.format(indicatorsxAxisFormat[index])) // yAxis label in SI or other
// Then, specify some parameters for each chart
.tooltipUnits(indicatorsUnits[index])
.tooltipFormat(d3.format(indicatorsxAxisFormat[index])) // tooltip format in SI or other
.title(indicatorsUnits[index] == "" ? indicatorsNames[index] :
`${indicatorsNames[index]} (${indicatorsUnits[index]})`);
// Finally, set chart data with async function calling stuff from DB
indicatorVars[indicator].chart.data( chartData[indicator]);
// create chart with passed options
d3.select(indicatorVars[indicator].container)
.call(indicatorVars[indicator].chart);
// Reload chart data and force chart update
indicatorVars[indicator].chartData = indicatorVars[indicator].chart.data(); // get chart data
indicatorVars[indicator].chart.data(indicatorVars[indicator].chartData); // set chart data
// Highlight plot title according to selected option
let option = $("#indicatorSelect").val(); // option selected from dropdrown
d3.select(indicatorVars[option].container).select("svg text.title").classed("active", true);
});
let map = mapData.map,
minIndicators = mapData.minIndicators,
maxIndicators = mapData.maxIndicators;
let cuencaLayer = L.geoJson(null, {
style: {
stroke: true,
weight: 5,
color: "grey",
opacity: .35,
fillOpacity: 0.05
},
interactive: false
});
let cuencaBufferMask = omnivore.topojson("data/buffer_cuenca.topojson", null, cuencaLayer);
cuencaBufferMask.addTo(map);
//map.createPane("wb-Tiles");
//map.getPane("wb-Tiles").style.zIndex = 450;
// create mvt layers
userFiles.forEach( f => {
f = mapboxLayer(f);
});
glmap = L.mapboxGL({
accessToken: "no-token",
style: {
"version": 8,
"sources": {},
"layers": []
}
}).addTo(map);
// after mapboxGL map is ready with styles do this:
glmap.getMapboxMap().on("style.load", () => {
const baseUrl = new URL(`/data`, window.location.href).href;
glmap.getMapboxMap().addSource("basecuerposagua", {
type: "vector",
tiles: [`${baseUrl}/basecuerposagua/mvt/{z}/{x}/{y}?geom_column=geom&columns=${indicators.join()},descrip`],
maxzoom: 14,
minzoom: 7
});
glmap.getMapboxMap().addLayer({
"id": "baseFill",
"source": "basecuerposagua",
"source-layer": "basecuerposagua",
"type": "fill",
"paint": {
"fill-opacity": 0.25,
"fill-color": "#aaaaaa"
}
});
glmap.getMapboxMap().addLayer({
"id": "baseLine",
"source": "basecuerposagua",
"source-layer": "basecuerposagua",
"type": "line",
"paint": {
"line-opacity": 0.75,
"line-width": ["interpolate", ["linear"], ["zoom"],
8, 0,
18, 4
],
"line-color": ["match",
["get", "descrip"],
"Áreas temporalmente inundadas", "#ff5500",
"Suelos húmedos-vegetación acuática", "#98e600",
"#00c5ff" // Agua permanente default
],
"line-dasharray": [2, 2]
}
});
/*map.on('click', function (e) {
var features = glmap.getMapboxMap().queryRenderedFeatures(e.point, {layers: ["cuerpos_enero_2016"]});
console.log(e, features)
features.forEach(f => {
// console.log(f.properties)
})
});*/
userFiles.forEach(monthYear => {
glmap.getMapboxMap().addLayer(currentTiles[monthYear]);
});
Object.keys(allTiles).forEach(layer => {
if (layer == userFiles[0]) {
glmap.getMapboxMap().setPaintProperty(layer, "fill-opacity", 0.25);
}
});
timeDimensionControl.addTo(map);
// Pass dummy geojson layer to timeDimension in order to register and sync
timeLayer = L.timeDimension.layer.Tile(L.geoJSON(), {
updateTimeDimension: true,
updateTimeDimensionMode: "replace",
waitForReady: true,
duration: "P1M"
});
timeLayer.addTo(map);
// style currentTiles
let option = $("#indicatorSelect").val(); // option selected from dropdrown
//styleTiles(option, minIndicators, maxIndicators)
//.then(legend.addTo(map)); // add legend control -> it updates
legend.addTo(map);
let baseLayers = {
"Carto Dark": cartoDarkLayer,
"Carto Light": cartoLightLayer,
"OpenStreetMap": osmLayer
};
var overlays = {
"<span id=\"cuencaOverlay\">Agua en la cuenca del Grijalva</span>": timeLayer
};
layerControl = L.control.layers(baseLayers, overlays).addTo(map);
makeBaseMap(); // basemap.js
// fix for leaflet-mapbox-gl v. 0.0.11 that adds map's to tile pane:
// get children of map tile pane, create array from it and iterate
// setting their z-index
let glmapChildren = map.getPanes().tilePane.children,
children = Array.from(glmapChildren);
children.forEach( c => c.style.zIndex = "inherit" );
});
}
const updateMap = (mapData) => {
//console.log(userFiles);
// clear tiles
currentTiles = {};
//retrieve or create tiles for current dates
userFiles.forEach( monthYear => {
if (Object.keys(allTiles).includes(monthYear)) {
currentTiles[monthYear] = allTiles[monthYear]; // recover tile if it has already been created
//currentJSONs[monthYear] = allJSONs[monthYear]; // recover json if it has already been created
return; // if file has already been processed, exit
} else { // if file cannot be found in allTiles, then add 1 to the number of files to process
let newTile = mapboxLayer(monthYear);
glmap.getMapboxMap().addLayer(newTile);
if (monthYear == userFiles[0]) {
glmap.getMapboxMap().setPaintProperty(monthYear, "fill-opacity", 0.7)
}
}
});
// update timeDimension times
timeLayer._timeDimension.setAvailableTimes(userDates, "replace");
timeLayer._timeDimension.setCurrentTime(mapData.min);
// clear minmax indicators objects
maxIndicators = {},
minIndicators = {};
indicators.forEach( (indicator) => {
maxIndicators[indicator] = 0;
minIndicators[indicator] = 1e30;
});
// query db for new minmax values then style tiles
new Promise( resolve => {
queryFiles().then( minmax => {
minmax.map( minmaxMonth => {
indicators.forEach( (indicator) => {
minIndicators[indicator] = Math.min(minIndicators[indicator], minmaxMonth[`min${indicator}`]);
maxIndicators[indicator] = Math.max(maxIndicators[indicator], minmaxMonth[`max${indicator}`]);
});
});
resolve({"map": map, "minIndicators": minIndicators, "maxIndicators": maxIndicators});
})
}).then( values => { // once we have new minmax values, style all tiles
let option = $("#indicatorSelect").val(), // option selected from dropdrown
min = values.minIndicators,
max = values.maxIndicators;
//styleTiles(option, min, max)
//.then(legend.addTo(map)); // add legend control -> it updates
});
// Update charts
updateCharts();
}
const updateCharts = async () => {
$(".loader").css("display", "block");
// TODO: add mask while fetching async data
// if user has drawn polygons, update chart with data inside selection
if (drawnItems.toGeoJSON().features.length != 0) {
let allData = await getDataInSelection();
indicators.map( async indicator => {
indicatorVars[indicator].chart.data( summarizeData(allData, indicator) );
});
} else {
// otherwise use all data
let allData = await getData();
indicators.map( async indicator => {
indicatorVars[indicator].chart.data( allData[indicator]);
});
}
$(".loader").hide("fade", 750);
}
// define MVT layer for given month table and all indicators
const mapboxLayer = (monthYear) => {
const baseUrl = new URL(`/data`, window.location.href).href;
let pbfLayer = {
id: monthYear,
source: {
type: "vector",
tiles: [`${baseUrl}/${monthYear}/mvt/{z}/{x}/{y}?geom_column=geom&columns=${indicators.join()},descrip`],
maxzoom: 14,
minzoom: 7
},
"source-layer": monthYear,
type: "fill",
"paint": {
"fill-opacity": 0,
"fill-color": ["match",
["get", "descrip"],
"Áreas temporalmente inundadas", "#ff5500",
"Suelos húmedos-vegetación acuática", "#98e600",
"#00c5ff" // Agua permanente default
]
}
}
currentTiles[monthYear] = pbfLayer;
allTiles[monthYear] = pbfLayer;
return pbfLayer;
}
const setupTimeDimensionControl = () => {
L.Control.TimeDimensionCustom = L.Control.TimeDimension.extend({
_getDisplayDateFormat: date => {
let d = new Date(date);
let year = d.getFullYear().toString();
let month = d.getUTCMonth();
return `${monthArray[month]} ${year}`;
}
});
timeDimensionControl = new L.Control.TimeDimensionCustom({
loopButton: true,
/*minSpeed: 1,
maxSpeed: 5,*/
timeSteps: 1,
playReverseButton: true,
//limitSliders: true,
playerOptions: {
//buffer: 5,
//minBufferReady: 5,
transitionTime: 125,
loop: true
},
timeZones: ["Local"]
});
}
L.TimeDimension.Layer.Tile = L.TimeDimension.Layer.extend({
_setAvailableTimes: function () {
if (this.options.times) {
return L.TimeDimension.Util.parseTimesExpression(this.options.times);
} else if (this.options.timeInterval) {
let tiArray = L.TimeDimension.Util.parseTimeInterval(this.options.timeInterval);
let period = this.options.period || "P1D";
let validTimeRange = this.options.validTimeRange || undefined;
//alert("times");
return L.TimeDimension.Util.explodeTimeRange(tiArray[0], tiArray[1], period, validTimeRange);
} else {
return [];
}
},
onAdd: function (map) {
// Don't call prototype so this_update() does not get called twice
//L.TimeDimension.Layer.prototype.onAdd.call(this, map);
this._map = map;
if (!this._timeDimension && map.timeDimension) {
this._timeDimension = map.timeDimension;
}
this._timeDimension.on("timeloading", this._onNewTimeLoading, this);
this._timeDimension.on("timeload", this._update, this);
this._timeDimension.registerSyncedLayer(this);
map.addLayer(this._baseLayer);
this._update();
},
onRemove: function (map) {
this._timeDimension.unregisterSyncedLayer(this);
this._timeDimension.off("timeloading", this._onNewTimeLoading, this);
this._timeDimension.off("timeload", this._update, this);
//this._baseLayer.getContainer().style.display = "none";
Object.keys(allTiles).forEach(layer => { // hide all tiles
glmap.getMapboxMap().setPaintProperty(layer, "fill-opacity", 0);
});
//this.eachLayer(map.removeLayer, map);
//this._map = null;
},
isReady: function (time) {
// to be implemented for each type of layer
return true;
},
_update: function () {
if (!this._baseLayer || !this._map) {
return;
}
var time = this._timeDimension.getCurrentTime();
// get data for time
let d = new Date(time),
year = d.getFullYear().toString(),
m = d.getUTCMonth(),
month = monthArray[m].toLowerCase(),
monthYear = `${month}_${year}`;
// Update title
let title = $("#title");
title.html(`<h2>Cobertura de agua en la cuenca del r&iacute;o Grijalva en ${month} de ${year}</h2>`);
// Update graphs only on timeload event
indicators.forEach( indicator => {
indicatorVars[indicator].chartData = indicatorVars[indicator].chart.data(); // get chart data
indicatorVars[indicator].chart.data(indicatorVars[indicator].chartData); // set chart data
});
//console.time("process");
//console.log("data for", monthYear);
//console.log(currentTiles)
Object.keys(allTiles).forEach(layer => {
if (layer.split("cuerpos_")[1] !== monthYear) { // hide all other months
glmap.getMapboxMap().setPaintProperty(layer, "fill-opacity", 0);
} else { // except current one
glmap.getMapboxMap().setPaintProperty(layer, "fill-opacity", 0.5);
}
});
//console.timeEnd("process");
}
});
L.timeDimension.layer.Tile = (layer, options) => {
return new L.TimeDimension.Layer.Tile(layer, options);
};
// When selecting indicator from dropdown, style tiles.
$("#indicatorSelect").on("change", function() {
// style currentTiles
let option = this.value; // option selected from dropdrown
//styleTiles(option, minIndicators, maxIndicators)
//.then(legend.addTo(map)); // add legend control -> it updates
// FIXME: re-adding control updates its contents... why?
// Highlight plot title according to selected option
indicators.forEach( indicator => {
d3.select(indicatorVars[indicator].container).select("svg text.title").classed("active", indicator === option ? true : false);
});
});
const styleTiles = (option, minIndicators, maxIndicators) => {
// define color scale domain based on min-max values for selected indicator
let domain = [minIndicators[option], maxIndicators[option]];
//console.log(domain)
scale = chroma.scale("PuBu").padding([0.5, 0]).domain(domain).classes(5);
Object.keys(currentTiles).forEach(layer => {
/*let color = [
"interpolate",
["linear"],
["get", option],
minIndicators[option], scale(minIndicators[option]).hex(),
maxIndicators[option], scale(maxIndicators[option]).hex()
];*/
let color = ["case",
["==", ["get", "descrip"], "Agua permanente"], "#00c5ff",
["==", ["get", "descrip"], "Áreas temporalmente inundadas"], "#ff5500",
["==", ["get", "descrip"], "Suelos húmedos-vegetación acuática"], "#98e600",
'#ff0000'
];
glmap.getMapboxMap().setPaintProperty(layer, "fill-color", color);
});
return Promise.resolve(scale);
}
let legend = L.control({
position: "bottomright"
});
legend.onAdd = () => {
let div = L.DomUtil.create("div", "info legend leaflet-bar");
/*let option = $("#indicatorSelect").val();
let optionIndex = indicators.indexOf(option);
let legendText = indicatorsUnits[optionIndex] == "" ? indicatorsNames[optionIndex] :
`${indicatorsNames[optionIndex]} (${indicatorsUnits[optionIndex]})`;
var html = `<h6>${legendText}</h6><ul>`;
let classes = scale.classes();*/
var html = "<h6>Cuerpos de agua</h6><ul>";
Object.keys(colors).forEach( (c, idx, array) => {
html += `<li><i style="background: ${colors[c].fill}"></i>${c}</li>`;
});
html += "</ul>";
div.innerHTML = html;
return div;
};
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment