public_library_map/website/load-map.js
Hugh Rundle 632534789d improve popups when clicking on libraries
Allows users to click on the location dots AND the 800m radius (location point is overlayed at long distances).
Also adds phone numbers to all popups and improves code readability.
Fixes #12
2021-01-19 08:17:22 +11:00

525 lines
15 KiB
JavaScript

// add tile layer from OSM
var baseMap = L.tileLayer('https://api.mapbox.com/styles/v1/{id}/tiles/{z}/{x}/{y}?access_token={accessToken}', {
attribution: 'Map data &copy; <a href="https://www.openstreetmap.org/">OpenStreetMap</a> contributors, <a href="https://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>, Imagery © <a href="https://www.mapbox.com/">Mapbox</a>',
maxZoom: 18,
id: 'mapbox/dark-v10',
tileSize: 512,
zoomOffset: -1,
accessToken: 'pk.eyJ1IjoiaHVnaHIiLCJhIjoiY2lxenRqMGQyMDJvdWZwbWd0d2JxeGswNiJ9.vfUQRJDzbJhaG_865TSkPA'
});
var baseRules = L.tileLayer('https://api.mapbox.com/styles/v1/{id}/tiles/{z}/{x}/{y}?access_token={accessToken}', {
attribution: 'Map data &copy; <a href="https://www.openstreetmap.org/">OpenStreetMap</a> contributors, <a href="https://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>, Imagery © <a href="https://www.mapbox.com/">Mapbox</a><br>Incorporates Administrative Boundaries ©PSMA Australia Limited licensed by the Commonwealth of Australia under Creative Commons Attribution 4.0 International licence (CC BY 4.0).',
maxZoom: 18,
id: 'mapbox/light-v10',
tileSize: 512,
zoomOffset: -1,
accessToken: 'pk.eyJ1IjoiaHVnaHIiLCJhIjoiY2lxenRqMGQyMDJvdWZwbWd0d2JxeGswNiJ9.vfUQRJDzbJhaG_865TSkPA'
});
// attach map to #mapid div above and centre
var map = L.map('mapid', {
center: [-27.00, 133.000],
zoom: 5,
layers: [baseMap]
});
// Use TopoJSON
// -----------------------------------------------------------------
L.TopoJSON = L.GeoJSON.extend({
addData: function (jsonData) {
if (jsonData.type === 'Topology') {
for (let key in jsonData.objects) {
geojson = topojson.feature(jsonData, jsonData.objects[key]);
L.GeoJSON.prototype.addData.call(this, geojson);
}
} else {
L.GeoJSON.prototype.addData.call(this, jsonData);
}
},
});
// // Copyright (c) 2013 Ryan Clark (MIT)
// -----------------------------------------------------------------
// library services fines overlay
var fines = new L.TopoJSON(libraryServices, {
style: function(feature){
return {
fillColor: getFinesColor(feature.properties.fines),
weight: 3,
color: "white",
dashArray: "4",
fillOpacity: 0.4
}
},
onEachFeature: function onEachFeature(feature, layer) {
layer.on({
mouseover: e => highlightFeature(e),
mouseout: e => resetHighlight(e, fines),
click: zoomToFeature
})
}
});
// fill patterns for loan period overlay
var circles = new L.PatternCircle({
color: '#000',
weight: 1,
radius: 2,
x: 4,
y: 4,
fill: true,
fillOpacity: 1
});
var loanTwo = new L.Pattern({
width: 8,
height: 8
})
loanTwo.addShape(circles);
loanTwo.addTo(map);
var loanThree = new L.StripePattern({
color: '#000'
});
loanThree.addTo(map);
var loanFour = new L.StripePattern({
color: '#000',
weight: 6,
spaceWeight: 2,
angle: 45
});
loanFour.addTo(map);
var loanSix = new L.StripePattern({
color: '#000',
weight: 2,
spaceWeight: 6,
angle: 135
});
loanSix.addTo(map);
function getLoanFillPattern(w) {
return w == '2' ? loanTwo :
w == '3' ? loanThree :
w == '4' ? loanFour :
w == '6' ? loanSix : null
}
// loan period overlay
var loanPeriod = new L.TopoJSON(libraryServices, {
style: function(feature){
return {
weight: 3,
color: "#fff",
dashArray: "4",
fillOpacity: 0.6,
fillColor: "#bbb",
fillPattern: getLoanFillPattern(feature.properties.standard_loan_weeks)
}
},
onEachFeature: function onEachFeature(feature, layer) {
layer.on({
mouseover: e => highlightFeature(e),
mouseout: e => resetHighlight(e, loanPeriod),
click: zoomToFeature
})
}
});
const branches = L.layerGroup([
L.geoCsv(branchesCsv, {
firstLineTitles: true,
fieldSeparator: ',',
onEachFeature: function (feature, layer) {
layer.bindPopup(
`<strong>${feature.properties.town}</strong>` +
`<p>${feature.properties.address}<br/>` +
`phone: ${feature.properties.phone}</p>`
)
},
pointToLayer: function (feature, latlng) {
return L.circle(latlng, {color: "#FF3961", radius: 800}) // this is an 800m radius around the library
}
}),
L.geoCsv(branchesCsv, {
firstLineTitles: true,
fieldSeparator: ',',
onEachFeature: function (feature, layer) {
layer.bindPopup(
`<strong>${feature.properties.town}</strong>` +
`<p>${feature.properties.address}<br/>` +
`phone: ${feature.properties.phone}</p>`
)
},
pointToLayer: function (feature, latlng) {
return L.circleMarker(latlng, {color: "#FF3961", radius: 2, fill: true})
}
})
]).addTo(map) // add this to the initial map on load
// Indigenous Knowledge Centre locations from csv file
const ikcs = L.layerGroup([
L.geoCsv(ikcCsv, {
firstLineTitles: true,
fieldSeparator: ',',
onEachFeature: function (feature, layer) {
layer.bindPopup(
`<strong>${feature.properties.town}</strong>` +
`<p>${feature.properties.address}<br/>` +
`phone: ${feature.properties.phone}</p>`
)
},
pointToLayer: function (feature, latlng) {
return L.circle(latlng, {color: "#76DBA7", radius: 800})
}
}),
L.geoCsv(ikcCsv, {
firstLineTitles: true,
fieldSeparator: ',',
onEachFeature: function (feature, layer) {
layer.bindPopup(
`<strong>${feature.properties.town}</strong>` +
`<p>${feature.properties.address}<br/>` +
`phone: ${feature.properties.phone}</p>`
)
},
pointToLayer: function (feature, latlng) {
return L.circleMarker(latlng, {color: "#76DBA7", radius: 2, fill: true})
}
})
]).addTo(map) // add this to the initial map on load
// mechanics institutes (Vic) & schools of arts (NSW) locations from csv file
const mechsAndSoA = L.layerGroup([
L.geoCsv(mechanics, {
firstLineTitles: true,
fieldSeparator: ',',
onEachFeature: function (feature, layer) {
layer.bindPopup(
`<strong>${feature.properties.town}</strong>` +
`<p>${feature.properties.address}<br/>` +
`phone: ${feature.properties.phone}</p>`
)
},
pointToLayer: function (feature, latlng) {
return L.circle(latlng, {color: "rgb(255,165,0)", radius: 800})
}
}),
L.geoCsv(mechanics, {
firstLineTitles: true,
fieldSeparator: ',',
onEachFeature: function (feature, layer) {
layer.bindPopup(
`<strong>${feature.properties.town}</strong>` +
`<p>${feature.properties.address}<br/>` +
`phone: ${feature.properties.phone}</p>`
)
},
pointToLayer: function (feature, latlng) {
return L.circleMarker(latlng, {color: "rgb(255,165,0)", radius: 2, fill: true})
}
})
]).addTo(map) // add this to the initial map on load
// NSLA locations from csv file
const otherLibs = L.layerGroup([
L.geoCsv(nslaBranches, {
firstLineTitles: true,
fieldSeparator: ',',
onEachFeature: function (feature, layer) {
layer.bindPopup(
`<strong>${feature.properties.town}</strong>` +
`<p>${feature.properties.address}<br/>` +
`phone: ${feature.properties.phone}</p>`
)
},
pointToLayer: function (feature, latlng) {
return L.circle(latlng, {color: "#75f857", radius: 800})
}
}),
L.geoCsv(nslaBranches, {
firstLineTitles: true,
fieldSeparator: ',',
onEachFeature: function (feature, layer) {
layer.bindPopup(
`<strong>${feature.properties.town}</strong>` +
`<p>${feature.properties.address}<br/>` +
`phone: ${feature.properties.phone}</p>`
)
},
pointToLayer: function (feature, latlng) {
return L.circleMarker(latlng, {color: "#75f857", radius: 2, fill: true})
}
})
]).addTo(map) // add this to the initial map on load
// ++++++++++++++
// control layers
// ++++++++++++++
var baseMaps = {
"Libraries" : baseMap,
// "Languages" : baseLang,
"Rules" : baseRules,
}
// change the overlay name depending on the mode.
var modeButton = document.getElementById('mode-button');
var overlayMaps = {
"Settler Knowledge Centres" : branches,
"Indigenous Knowledge Centres": ikcs,
"Worker Pacification Centres" : mechsAndSoA,
"Imperial Knowledge Centres": otherLibs
}
function setGeneral() {
overlayMaps = {
"Settler Knowledge Centres" : branches,
"Indigenous Knowledge Centres": ikcs,
"Worker Pacification Centres" : mechsAndSoA,
"Imperial Knowledge Centres": otherLibs
}
modeButton.innerText = "View in White Fragility mode";
}
function setFragile() {
overlayMaps = {
"Public Libraries" : branches,
"Indigenous Knowledge Centres": ikcs,
"Mechanics Institutes" : mechsAndSoA,
"National & State Libraries" : otherLibs
};
modeButton.innerText = "View in General mode";
}
if (sessionStorage.getItem('mapMode') === 'fragile') {
setFragile()
} else {
setGeneral()
}
// switching mode between standard and fragile
function switchMode() {
if (sessionStorage.getItem('mapMode') === 'fragile') {
sessionStorage.setItem('mapMode', 'general');
setGeneral()
mapControl.remove();
infoBoxes.branches.remove()
mapControl = L.control.layers(baseMaps, overlayMaps, {"collapsed": false}).addTo(map);
infoBoxes.branches.addTo(map)
} else {
sessionStorage.setItem('mapMode', 'fragile');
setFragile()
mapControl.remove();
infoBoxes.branches.remove()
mapControl = L.control.layers(baseMaps, overlayMaps, {"collapsed": false}).addTo(map);
infoBoxes.branches.addTo(map)
}
}
modeButton.addEventListener('click', switchMode, false);
// add control layers
var mapControl = L.control.layers(
baseMaps,
overlayMaps,
{ "collapsed" : false }
).addTo(map);
// scale
L.control.scale().addTo(map);
// info boxes
var infoBoxes = {
branches: L.control(),
fines: L.control(),
loanPeriod: L.control(),
serviceInfo: L.control({position: 'topleft'})
}
function getFinesColor(f) {
return f == 'no' ? '#4dac26' :
f == 'yes' ? '#d01c8b' :
f == 'adults' ? '#f1b6da' :
f == 'by_lga' ? '#abd9e9' :
f == 'no_unconfirmed' ? '#b8e186' : '#bbb';
}
// highlight feature on mouse hover
function highlightFeature(e) {
var layer = e.target;
layer.setStyle({
weight: 6,
color: '#FF3961',
dashArray: "0",
fillOpacity: 0
});
if (!L.Browser.ie && !L.Browser.opera && !L.Browser.edge) {
layer.bringToFront();
}
infoBoxes.serviceInfo.addTo(map)
infoBoxes.serviceInfo.update(layer.feature.properties);
}
// clear on mouseout
function zoomToFeature(e) {
map.fitBounds(e.target.getBounds());
}
function resetHighlight(e, layer) {
layer.resetStyle(e.target);
infoBoxes.serviceInfo.remove()
}
// this is used to add general info when each layer is added
function addLegend() {
this._div = L.DomUtil.create('div', 'info')
this.update();
return this._div;
}
// FINES LEGEND
infoBoxes.fines.onAdd = addLegend;
infoBoxes.fines.update = function (props) {
this._div.innerHTML =
`<p>Hover over an area for more information</p>
<section>
<div><div class="circle" style="background-color: #4dac26"></div>Fine free</div>
<div><div class="circle" style="background-color: #b8e186"></div>Fine free (unconfirmed)</div>
<div><div class="circle" style="background-color: #f1b6da"></div>Fine free for children</div>
<div><div class="circle" style="background-color: #abd9e9"></div>Fine policy varies</div>
<div><div class="circle" style="background-color: #d01c8b"></div>Fines for all users</div>
<div><div class="circle" style="background-color: #bbb"></div>Unknown (help me find out!)</div>
</section>
`
};
// BRANCH LOCATIONS LEGEND
infoBoxes.branches.onAdd = addLegend;
infoBoxes.branches.update = function (props) {
this._div.innerHTML = `
<h4>Library Branches</h4>
<p>Circles represent an 800 metre radius from the library location. This is the distance generally used by urban planners to represent "conceptually within walking distance" for most people.</p>
`};
infoBoxes.branches.addTo(map) // add by default
// STANDARD LOAN PERIOD LEGEND
infoBoxes.loanPeriod.onAdd = addLegend;
infoBoxes.loanPeriod.update = function (props) {
this._div.innerHTML = `
<section>
<div><div class="circle" style="background:
radial-gradient(4px 4px at 6px 6px, #3a3a3a 50%, transparent 75%),
radial-gradient(4px 4px at 16px 6px, #3a3a3a 50%, transparent 75%),
radial-gradient(4px 4px at 2px 12px, #3a3a3a 50%, transparent 75%),
radial-gradient(4px 4px at 12px 12px, #3a3a3a 50%, transparent 75%),
radial-gradient(4px 4px at 20px 12px, #3a3a3a 50%, transparent 75%),
radial-gradient(4px 4px at 8px 18px, #3a3a3a 50%, transparent 75%);
background-repeat: no-repeat;
border: 1px solid black;
"></div>2 weeks</div>
<div><div class="circle" style="background: repeating-linear-gradient(
0deg,
#3a3a3a,
#3a3a3a 2px,
#fff 2px,
#3a3a3a 4px
)"></div>3 weeks</div>
<div><div class="circle" style="background: repeating-linear-gradient(
45deg,
#3a3a3a,
#3a3a3a 3px,
#fff 1px,
#3a3a3a 4px
)"></div>4 weeks</div>
<div><div class="circle" style="background: repeating-linear-gradient(
135deg,
#fff,
#fff 8px,
#3a3a3a 2px,
#fff 10px
)"></div>6 weeks</div>
<div><div class="circle" style="background-color: #bbb"></div>Unknown (help me find out!)</div>
</section>
`
};
// FOCUSSED AREA INFOBOX
infoBoxes.serviceInfo.onAdd = addLegend;
infoBoxes.serviceInfo.update = function(props) {
if (props) {
this._div.innerHTML = `<h4>${props.name}</h4>` +
'<section><p>' +
(
props.fines === "no" ? "Fine free for overdue items" :
props.fines == "no_unconfirmed" ? "Probably no overdue fines" :
props.fines === "yes" ? "Overdue fines for all users" :
props.fines == "adults" ? "No overdue fines for children" :
props.fines == "by_lga" ? "Fine policy varies" : "No fines data"
) + '</p><p>' +
(
!props.standard_loan_weeks || props.standard_loan_weeks == "?" ? `No loan period data` : `${props.standard_loan_weeks} week loans` +
'</p></section>'
)}
}
// remove info boxes & markers when relevant layer is removed
map.on('overlayremove', l => {
if (l.name == "Fines") {
infoBoxes.fines.remove()
}
if (l.name == "Loan Period") {
infoBoxes.loanPeriod.remove()
}
})
// add info boxes & markers when relevant layer is added
map.on('overlayadd', l => {
if (l.name == "Fines") {
infoBoxes.fines.addTo(map)
}
if (l.name == "Loan Period") {
infoBoxes.loanPeriod.addTo(map)
loanPeriod.bringToBack()
}
})
// change overlays depending on base layer
// we remove info boxes before adding them again where relevant
// this is so we don't accidentally stack up multiple copies dependng on user
// navigation journey
map.on('baselayerchange', l => {
for (let k in infoBoxes) {
infoBoxes[k].remove()
}
if (l.name == "Rules") {
mapControl.addOverlay(fines, "Fines")
mapControl.addOverlay(loanPeriod, "Loan Period")
loanPeriod.addTo(map)
fines.addTo(map)
for (let i in overlayMaps ) {
mapControl.removeLayer(overlayMaps[i])
overlayMaps[i].remove()
}
modeButton.setAttribute('class', 'hidden'); // hide the mode button when it's not relevant
} else {
mapControl.removeLayer(fines)
mapControl.removeLayer(loanPeriod)
fines.remove()
loanPeriod.remove()
branches.addTo(map)
for (let k in overlayMaps ) {
mapControl.addOverlay(overlayMaps[k], k)
}
infoBoxes.branches.addTo(map);
modeButton.setAttribute('class', 'visible');
}
})