Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
F
fordecyt_2019
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Rodrigo Tapia-McClung
fordecyt_2019
Commits
8670b050
Commit
8670b050
authored
Aug 27, 2019
by
Rodrigo Tapia-McClung
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
pgserver tiles and leaflet
parent
4f4e017d
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
510 additions
and
0 deletions
+510
-0
map.css
public/map/css/map.css
+44
-0
index.html
public/map/index.html
+34
-0
functions.js
public/map/js/functions.js
+432
-0
No files found.
public/map/css/map.css
0 → 100644
View file @
8670b050
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
public/map/index.html
0 → 100644
View file @
8670b050
<!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>
public/map/js/functions.js
0 → 100644
View file @
8670b050
/*
* 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
:
"<Ant"
,
nextText
:
"Sig>"
,
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ércoles"
,
"Jueves"
,
"Viernes"
,
"Sábado"
],
dayNamesShort
:
[
"Dom"
,
"Lun"
,
"Mar"
,
"Mié"
,
"Juv"
,
"Vie"
,
"Sáb"
],
dayNamesMin
:
[
"Do"
,
"Lu"
,
"Ma"
,
"Mi"
,
"Ju"
,
"Vi"
,
"Sá"
],
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
]
=
1
e30
;
$
(
"#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í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
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment