Commit 58ebdc06 authored by Rodrigo Tapia-McClung's avatar Rodrigo Tapia-McClung

Reorder public folder structure

parent 136bf697
<!DOCTYPE html>
<html lang="es">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Proyecto FORDECyT 2018 - 10</title>
<link rel="stylesheet" type="text/css" href="css/font-awesome.min.css">
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.1/css/all.css" integrity="sha384-50oBUHEmvpQ+1lW4y57PTFmhCaXp0ML5d60M1M7uH2+nqUivzIebhndOJK28anvf" crossorigin="anonymous">
<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" type="text/css" href="css/cover_style.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/github-fork-ribbon-css/0.2.3/gh-fork-ribbon.min.css" />
<link rel="icon" href="img/monitoreo.png" sizes="16x16">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-light bg-light sb-navbar">
<div class="container">
<a class="navbar-brand" href="http://www.centrogeo.org.mx/">
<img src="img/centrogeo.png" height="70" alt="CentroGeo">
</a>
<a class="mr-sm-2" href="http://conacyt.gob.mx/">
<img src="img/conacyt.png" height="70" alt="CONACyT">
</a>
</nav>
<div>
<!--<div class="d-flex flex-column flex-md-row align-items-center p-3 px-md-4 mb-3 bg-white border-bottom shadow-sm">
<h5 class="my-0 mr-md-auto font-weight-normal">Centro de Investigaci&oacute;n en Ciencias de Informaci&oacute;n Espacial</h5>
<nav class="my-2 my-md-0 mr-md-3">
<a class="p-2 text-dark" href="#">Features</a>
<a class="p-2 text-dark" href="#">Enterprise</a>
<a class="p-2 text-dark" href="#">Support</a>
<a class="p-2 text-dark" href="#">Pricing</a>
</nav>
<a class="btn btn-outline-primary" href="#">Sign up</a>-->
</div>
<div class="pricing-header px-3 py-3 pt-md-5 pb-md-4 mx-auto text-center">
<h1 class="display-5">Proyecto FORDECyT 2018 - 10</h1>
<p class="lead">An&aacute;lisis y Monitoreo del Crecimiento del Medio Urbano y del Comportamiento de Cuerpos de Agua desde un Enfoque de Sustentabilidad. Casos de Estudio: Corredor Metropolitano Centro País y Cuenca del r&iacute;o Grijalva.
</p>
</div>
<div class="container">
<div class="card-deck mb-3 text-center">
<div class="row">
<div class="card mb-4 shadow-sm">
<div class="card-header">
<h4 class="my-0 font-weight-normal">R&iacute;o Grijalva</h4>
</div>
<div class="card-body">
<div class="hovereffect zoom">
<img class="card-img" src="img/sumidero.jpg" alt="Ca&ntilde;&oacute;n del Sumidero">
<span class="photo-credit">Foto: Gabriela L&oacute;pez</span>
<div class="overlay">
<a class="info" href="grijalva/">
<img class="hoverimg mx-auto d-block" src="img/cuenca.png" width="325px">
<span class="btn btn-primary hoverbtn" href="grijalva/" role="button">Visitar
sitio</span>
<br><br>
</a>
</div>
</div>
</div>
</div>
<div class="card mb-4 shadow-sm">
<span class="github-fork-ribbon left-bottom" data-ribbon="En construcción" title="En construcción">En construcción</span>
<div class="card-header">
<h4 class="my-0 font-weight-normal">Riesgo</h4>
</div>
<div class="card-body">
<div class="hovereffect zoom">
<img class="card-img" src="img/inundacion.jpg" alt="Ca&ntilde;&oacute;n del Sumidero">
<span class="photo-credit">Foto: Felinto C&oacute;rdoba</span>
<div class="overlay">
<a class="info" href="riesgos/">
<img class="hoverimg mx-auto d-block" src="img/cuenca.png" width="325px">
<span class="btn btn-primary hoverbtn" href="grijalva/" role="button">Visitar
sitio</span>
<br><br>
</a>
</div>
</div>
</div>
</div>
</div>
<div class="row justify-content-md-center">
<div class="col-6">
<div class="card mb-4 shadow-sm">
<span class="github-fork-ribbon left-bottom" data-ribbon="En construcción" title="En construcción">En construcción</span>
<div class="card-header">
<h4 class="my-0 font-weight-normal">Corredor Metropolitano Centro Pa&iacute;s</h4>
</div>
<div class="card-body">
<div class="hovereffect zoom">
<img class="card-img" src="img/centropais.jpg" alt="CMCP">
<span class="photo-credit">Foto: Lucy Nieto</span>
<div class="overlay">
<a class="info" href="centropais/">
<img class="hoverimg mx-auto d-block" src="img/cmcp.png" width="325px">
<span class="btn btn-primary hoverbtn" href="centropais/" role="button">Visitar
sitio</span>
<br><br>
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<footer class="pt-4 my-md-5 pt-md-5 border-top">
<div class="row">
<div class="col-12 col-md">
<img class="mb-2" src="img/geoint.png" alt="GeoInt" height="70px">
<small class="d-block mb-3 text-muted">&copy; 2019</small>
</div>
<div class="col-6 col-md">
<h5>Features</h5>
<ul class="list-unstyled text-small">
<li><a class="text-muted" href="#">Cool stuff</a></li>
<li><a class="text-muted" href="#">Random feature</a></li>
<li><a class="text-muted" href="#">Team feature</a></li>
<li><a class="text-muted" href="#">Stuff for developers</a></li>
<li><a class="text-muted" href="#">Another one</a></li>
<li><a class="text-muted" href="#">Last time</a></li>
</ul>
</div>
<div class="col-6 col-md">
<h5>Resources</h5>
<ul class="list-unstyled text-small">
<li><a class="text-muted" href="#">Resource</a></li>
<li><a class="text-muted" href="#">Resource name</a></li>
<li><a class="text-muted" href="#">Another resource</a></li>
<li><a class="text-muted" href="#">Final resource</a></li>
</ul>
</div>
<div class="col-6 col-md">
<h5>Acerca de</h5>
<ul class="list-unstyled text-small">
<li><a class="text-muted" href="#">Equipo</a></li>
<li><a class="text-muted" href="#">Desarrollo</a></li>
<li><a class="text-muted" href="#">Tecnolog&iacute;a</a></li>
<li><a class="text-muted" href="#">Terms</a></li>
</ul>
</div>
</div>
</footer>
</div>
</body>
</html>
\ No newline at end of file
body {
margin:0;
padding:0;
}
#mapmex {
position:absolute;
top:0;
bottom:0;
width:100%;
}
.picker {
left: 50px;
top: 45px;
position: absolute;
z-index: 400;
background: #cbddf3;
border: 1px solid #999;
padding: 4px;
border-radius: 5px;
}
#date-initial, #date-final {
text-align: center;
}
#datePickers{
/*display: none;*/
position: absolute;
/* width: 285px; */
z-index: 401;
background: #cbddf3;
border: 1px solid #999;
padding: 4px;
border-radius: 5px;
/*box-shadow: 0px 0px 15px #999;*/
top: 13px;
left: 50px;
}
.ui-datepicker-calendar {
display: none;
}
\ No newline at end of file
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Leaflet JS Example</title>
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no" />
<link rel="stylesheet" type="text/css" 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="./css/map.css" type="text/css">
</head>
<body>
<div id="mapmex"></div>
<div class="picker">
<select id="indicatorSelect"></select>
</div>
<div id="datePickers">
<input type="text" name="date-initial" id="date-initial" readonly="readonly" size="12"
placeholder="Fecha inicial" data-calendar="false" />
<input type="text" name="date-final" id="date-final" readonly="readonly" size="12"
placeholder="Fecha final" data-calendar="true" />
</div>
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
<script src="https://unpkg.com/leaflet@1.5.1/dist/leaflet.js"></script>
<script src="https://unpkg.com/leaflet.vectorgrid@latest/dist/Leaflet.VectorGrid.bundled.js"></script>
<script type="text/javascript" src="https://cdn.rawgit.com/nezasa/iso8601-js-period/master/iso8601.min.js"></script>
<script type="text/javascript" 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="./js/functions.js"></script>
</body>
</html>
/*
* Copyright 2019 - All rights reserved.
* Rodrigo Tapia-McClung
*
* August 2019
*/
/* global Promise, chroma */
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;
// define empty objects and indicators
let maxIndicators = {},
minIndicators = {},
indicators = ["area", "perimeter", "costa", "df"],
indicatorsNames = ["Área", "Perímetro", "Desarrollo de la línea de costa", "Dimensión fractal"];
let currentTiles = {};
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("%B_%Y");
setupTimeDimensionControl();
setupDates()
.then(dates => populateDates(dates))
.then(userDates => setupMap(userDates))
.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 => {
let layersQuery = "http://localhost:8090/data/list_layers";
d3.json(layersQuery).then(layers => {
layers.forEach(layer => {
dateArray.push(timeParse(layer.f_table_name)); // 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.forEach(time => {
userFiles.push(timeFormat(time));
});
resolve({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) => {
map = L.map("mapmex", {
center: [17.22, -92.28],
minZoom: 7,
zoom: 7,
timeDimension: true,
timeDimensionOptions: {
times: userDates,
currentTime: dates.min
}
});
let 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."
});
cartoDarkLayer.addTo(map);
map.createPane("wb-Tiles");
map.getPane("wb-Tiles").style.zIndex = 450;
// initialize min and max objects to hold values for each indicator
// and add select options
indicators.forEach( (indicator, index) => {
maxIndicators[indicator] = 0;
minIndicators[indicator] = 1e30;
$("#indicatorSelect").append("<option value=\""+ indicator + "\">" + indicatorsNames[index] + "</option>");
});
console.log(userFiles);
let cols = [];
indicators.forEach( (indicator) => {
cols.push("min(" + indicator + ") as min" + indicator);
cols.push("max(" + indicator + ") as max" + indicator);
});
// query db to get min/max values per month and indicator and store them in an object
userFiles.forEach( (table) => {
let minmaxQuery = "http://localhost:8090/data/query/" + table + "?columns=" + cols.join(", ");
d3.json(minmaxQuery).then(minmax => {
indicators.forEach( (indicator) => {
minIndicators[indicator] = Math.min(minIndicators[indicator], minmax[0]["min"+indicator]);
maxIndicators[indicator] = Math.max(maxIndicators[indicator], minmax[0]["max"+indicator]);
});
});
});
timeDimensionControl.addTo(map);
return new Promise( resolve => {
resolve(map);
});
}
const populateMap = (map) => {
// create mvt layers
userFiles.forEach( f => {
//f = mvtLayer(f, "area");
f = mvtLayer(f);
//abril_2018.addTo(map);
});
// style currentTiles
let option = $("#indicatorSelect").val(); // option selected from dropdrown
styleTiles(option);
let timeLayer = L.timeDimension.layer.Tile(currentTiles[userFiles[0]], {
updateTimeDimension: true,
updateTimeDimensionMode: "replace",
waitForReady: true,
duration: "P1M"
});
timeLayer.addTo(map);
Object.keys(currentTiles).forEach(layer => {
if (layer !== userFiles[0]) {
currentTiles[layer].getContainer().style.display = "none";
}
});
}
// define MVT layer for given month table and indicator
//const mvtLayer = (monthYear, indicator) => {
// define MVT layer for given month table
const mvtLayer = (monthYear) => {
let tiles = "http://localhost:8090/data/" + monthYear + "/mvt/{z}/{x}/{y}?geom_column=geom&columns=" + indicators.join();
let pbfLayer = L.vectorGrid.protobuf(tiles, {
pane: "wb-Tiles",
rendererFactory: L.canvas.tile,
/*vectorTileLayerStyles: {
[monthYear]: properties => { // use [var] to use a computed property name for monthYear...
let currentValue = properties[indicator];
let scale = chroma.scale("PuBu").padding([0.5, 0]).domain([minIndicators[indicator], maxIndicators[indicator]]).classes(5);
return {
fill: true,
fillOpacity: 0.7,
stroke: true,
weight: 1,
opacity: 0.2,
fillColor: scale(currentValue).hex(),
color: scale(currentValue).hex()
}
}
},*/
maxZoom: 22,
tolerance: 5,
extent: 4096,
buffer: 64,
debug: 0,
indexMaxZoom: 5,
indexMaxPoints: 100000,
interactive: true,
getFeatureId: f => {
return f.properties.id;
}
}).on('load', () => { // check when layer has loaded
// return promise to check when all layers have
// loaded and then can remove loader mask
//resolve("Tiles loaded!");
});
currentTiles[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
//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);
// Don't update on add. Rather check @708 and what happens there
//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";
//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(currentTiles).forEach(layer => {
if (layer !== monthYear) {
currentTiles[layer].getContainer().style.display = "none";
} else {
this._baseLayer = currentTiles[layer];
this._baseLayer.getContainer().style.display = "block";
}
});
//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)
// .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 => {
// define color scale domain based on min-max values for selected indicator
let domain = [minIndicators[option], maxIndicators[option]];
let scale = chroma.scale("PuBu").padding([0.5, 0]).domain(domain).classes(5);
Object.keys(currentTiles).forEach(layer => { // change style for each tile layer
currentTiles[layer].options.vectorTileLayerStyles[layer] = (properties, zoom) => {
let selectedIndicator = properties[option]; // choose column to style with
return {
fill: true,
fillOpacity: 0.5,
stroke: true,
weight: 1,
opacity: 0.5,
fillColor: scale(selectedIndicator).hex(),
color: scale(selectedIndicator).hex()
}
}
// redraw() causes additional tile requests... not good!
currentTiles[layer].redraw();
currentTiles[layer].addTo(map).setZIndex(4); // add tiles to map
});
return Promise.resolve(scale);
}
// TODO: add ordered date selector based on what's available on the DB
// TODO: add layer control
/*
* Copyright 2019 - All rights reserved.
* Rodrigo Tapia-McClung
*
* August 2019
*/
/* global Promise, chroma */
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,
glmap,
timeLayer;
// define empty objects and indicators
let maxIndicators = {},
minIndicators = {},
indicators = ["area", "perimeter", "costa", "df"],
indicatorsNames = ["Área", "Perímetro", "Desarrollo de la línea de costa", "Dimensión fractal"];
let cols = [];
indicators.forEach( (indicator) => {
cols.push("min(" + indicator + ") as min" + indicator);
cols.push("max(" + indicator + ") as max" + indicator);
});
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("%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 => {
let layersQuery = "http://localhost:8090/data/list_layers";
d3.json(layersQuery).then(layers => {
layers.forEach(layer => {
dateArray.push(timeParse(layer.f_table_name)); // 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
// TODO: on close trigger 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 => {
map = L.map("mapmex", {
center: [17.22, -92.28],
minZoom: 7,
zoom: 7,
timeDimension: true,
timeDimensionOptions: {
times: userDates,
currentTime: dates.min
}
});
let 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."
});
cartoDarkLayer.addTo(map);
// initialize min and max objects to hold values for each indicator
// and add select options
indicators.forEach( (indicator, index) => {
maxIndicators[indicator] = 0;
minIndicators[indicator] = 1e30;
$("#indicatorSelect").append("<option value=\""+ indicator + "\">" + indicatorsNames[index] + "</option>");
});
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]);
});
});
timeDimensionControl.addTo(map);
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 => {
let minmaxQuery = "http://localhost:8090/data/query/" + table + "?columns=" + cols.join(", ");
d3.json(minmaxQuery).then( minmax => {
resolve(minmax[0]);
});
});
}
const updateMap = (mapData) => {
//console.log(mapData);
console.log(userFiles);
// ckear 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._glMap.addLayer(newTile);
if (monthYear == userFiles[0]) {
glmap._glMap.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);
});
}
const populateMap = (mapData) => {
let map = mapData.map,
minIndicators = mapData.minIndicators,
maxIndicators = mapData.maxIndicators;
// 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._glMap.on("style.load", () => {
userFiles.forEach(monthYear => {
glmap._glMap.addLayer(currentTiles[monthYear]);
});
Object.keys(allTiles).forEach(layer => {
if (layer == userFiles[0]) {
glmap._glMap.setPaintProperty(layer, 'fill-opacity', 0.7)
}
});
// 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);
});
}
// define MVT layer for given month table and all indicators
const mapboxLayer = (monthYear) => {
let pbfLayer = {
id: monthYear,
source: {
type: 'vector',
tiles: ["http://localhost:8090/data/" + monthYear + "/mvt/{z}/{x}/{y}?geom_column=geom&columns=" + indicators.join()],
maxzoom: 14,
minzoom: 5
},
'source-layer': monthYear,
type: 'fill',
minzoom: 5,
'paint': {
'fill-opacity': 0,
/*'fill-color': [
'interpolate',
['linear'],
['get', 'df'],
1, 'rgba(255, 0, 0, 0.5)', // red
1.3, 'rgba(0, 255, 0, 0.5)', // green
]/*,
'fill-outline-color': [
'interpolate',
['linear'],
['get', 'df'],
1, 'rgba(255, 0, 0, 0.6)', // red
1.3, 'rgba(0, 255, 0, 0.6)', // green
]*/
}
}
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
//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);
// Remove base layer caue mapbox layer is not an L layer...
map.addLayer(this._baseLayer);
// Don't update on add. Rather check @708 and what happens there
//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";
//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 !== monthYear) { // hide all other months
glmap._glMap.setPaintProperty(layer, 'fill-opacity', 0);
} else { // except current one
glmap._glMap.setPaintProperty(layer, 'fill-opacity', 0.7);
}
});
//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)
let 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()
];
glmap._glMap.setPaintProperty(layer, 'fill-color', color);
});
return Promise.resolve(scale);
}
// TODO: add layer control
// TODO: add charts
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8' />
<title>Mapbox GL JS Examples</title>
<meta name='viewport' content='initial-scale=1,maximum-scale=1,user-scalable=no' />
<link rel="stylesheet" type="text/css" 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 href='https://api.tiles.mapbox.com/mapbox-gl-js/v1.1.0/mapbox-gl.css' rel='stylesheet' />
<link rel="stylesheet" href="./css/map.css" type="text/css">
</head>
<body>
<div id='mapmex'></div>
<div class="picker">
<select id="indicatorSelect"></select>
</div>
<div id="datePickers">
<input type="text" name="date-initial" id="date-initial" readonly="readonly" size="12"
placeholder="Fecha inicial" data-calendar="false" />
<input type="text" name="date-final" id="date-final" readonly="readonly" size="12"
placeholder="Fecha final" data-calendar="true" />
</div>
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
<script src="https://unpkg.com/leaflet@1.5.1/dist/leaflet.js"></script>
<script src="https://unpkg.com/leaflet.vectorgrid@latest/dist/Leaflet.VectorGrid.bundled.js"></script>
<script type="text/javascript" src="https://cdn.rawgit.com/nezasa/iso8601-js-period/master/iso8601.min.js"></script>
<script type="text/javascript" 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/functions_mapbox.js"></script>
<!--<script>
var map = new mapboxgl.Map({
'container': 'mapmex',
'zoom': 7,
'center': [-92.28, 17.22], // cuenca Grijalva
'style': {
'version': 8,
'sources': {
'carto-dark': {
'type': 'raster',
'tiles': [
"http://a.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png",
"http://b.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png",
"http://c.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png",
"http://d.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png"
]
}
},
'layers': [{
'id': 'carto-dark-layer',
'type': 'raster',
'source': 'carto-dark',
'minzoom': 0,
'maxzoom': 22
}]
}
});
map.addControl(new mapboxgl.NavigationControl());
map.on('load', function() {
map.addLayer({
id: 'abril_2018-tiles',
source: {
type: 'vector',
// "http://localhost:8090/data/" + monthYear + "/mvt/{z}/{x}/{y}?geom_column=geom&columns=" + indicators.join();
tiles: ['http://localhost:8090/data/abril_2018/mvt/{z}/{x}/{y}?geom_column=geom&columns=df'],
maxzoom: 14,
minzoom: 5
},
'source-layer': 'abril_2018',
type: 'fill',
minzoom: 5,
'paint': {
'fill-opacity': 0.7,
'fill-color': [
'interpolate',
['linear'],
['get', 'df'],
1, 'rgba(255, 0, 0, 0.5)', // red
1.3, 'rgba(0, 255, 0, 0.5)', // green
],
'fill-outline-color': [
'interpolate',
['linear'],
['get', 'df'],
1, 'rgba(255, 0, 0, 0.6)', // red
1.3, 'rgba(0, 255, 0, 0.6)', // green
]
}
})
})
</script>-->
</body>
</html>
\ 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