summaryrefslogtreecommitdiff
path: root/assets/mapper.js
diff options
context:
space:
mode:
Diffstat (limited to 'assets/mapper.js')
-rw-r--r--assets/mapper.js485
1 files changed, 485 insertions, 0 deletions
diff --git a/assets/mapper.js b/assets/mapper.js
new file mode 100644
index 0000000..ac1e2fc
--- /dev/null
+++ b/assets/mapper.js
@@ -0,0 +1,485 @@
+/**
+ * Wallonie#Demain Prototype
+ *
+ * Requires jQuery
+ **/
+
+// We don't have working GeoJSON support yet
+USE_GEOJSON = false
+
+// Subset of Dewey Maps' Deck
+// See https://lite.framacalc.org/XNQBPjpQuO
+const Categories = [
+ {"id":6, "name":"1. S'alimenter", "subcategories":[
+ {"name":"Marchés", "id":170},
+ {"name":"Ateliers cuisine", "id":180},
+ {"name":"Spots 'plantes aromatiques'", "id":155},
+ {"name":"Épiceries solidaires", "id":135},
+ {"name":"Eau potable", "id":115},
+ {"name":"Repas gratuits", "id":154},
+ {"name":"Restaurants sociaux", "id":118},
+ {"name":"Initiatives de récup' alimentaire", "id":28},
+ {"name":"GASAP (groupes d'achat)", "id":27},
+ {"name":"GASAP (producteurs)", "id":185}
+ ], "color":"#FFEB00"},
+
+ {"id":11, "name":"2. Se laver, s'habiller", "subcategories":[
+ {"name":"Boîtes à dons", "id":210},
+ {"name":"Vestiaires sociaux", "id":156},
+ {"name":"Donneries, marchés gratuits", "id":46},
+ {"name":"Douches publiques", "id":144},
+ {"name":"Friperies, vêtements de 2ème main", "id":104}
+ ], "color":"#7CFB80"},
+
+ {"id":10, "name":"3. Guérir, se soigner", "subcategories":[
+ {"name":"Maisons médicales", "id":18},
+ {"name":"Réseaux de santé", "id":145},
+ {"name":"Services de santé pour personnes précaires", "id":157}
+ ], "color":"#02ACCC"},
+
+ {"id":15, "name":"4. Recycler, réparer", "subcategories":[
+ {"name":"Ateliers de travail du bois", "id":138},
+ {"name":"Récup' de cartouches d'imprimantes", "id":136},
+ {"name":"Boîtes à livres", "id":50},
+ {"name":"Aide au compostage", "id":132},
+ {"name":"Repair cafés", "id":42},
+ {"name":"Récup' de matériaux de construction", "id":113},
+ {"name":"Bulles à vêtements", "id":206},
+ {"name":"Matériaux informatiques recyclés", "id":47},
+ {"name":"Recyclage de verre", "id":205},
+ {"name":"Marchés aux puces", "id":120}
+ ], "color":"#97C000"},
+
+ {"id":7, "name":"5. respirer, se mettre au vert", "subcategories":[
+ {"name":"Soutien aux potagistes", "id":128},
+ {"name":"Composts collectifs ou 'de quartier'", "id":121},
+ {"name":"Potagers & vergers", "id":116},
+ {"name":"Réserves naturelles", "id":133},
+ {"name":"Associations de naturalistes", "id":194},
+ {"name":"Parcs publics", "id":129},
+ {"name":"Grainothèques, bourses aux semences", "id":125},
+ {"name":"Associations apicoles", "id":127}
+ ], "color":"#7E8A0E"},
+
+ {"id":3, "name":"6. Se rencontrer, s'entraider", "subcategories":[
+ {"name":"Comités de quartiers", "id":102},
+ {"name":"Associations de soutien aux seniors", "id":19},
+ {"name":"Maisons de Jeunes", "id":17},
+ {"name":"Soutien à l'enfance et à la famille", "id":14},
+ {"name":"Maisons de quartiers", "id":10},
+ {"name":"Monnaies complémentaires", "id":211},
+ {"name":"Associations de soutien scolaire", "id":119},
+ {"name":"Associations de Femmes", "id":15},
+ {"name":"Accueil des réfugiés", "id":188},
+ {"name":"Soutien aux personnes en situation d'handicap", "id":16},
+ {"name":"Accueil des primo-arrivants", "id":197},
+ {"name":"Coopération et solidarité internationale", "id":209},
+ {"name":"Associations pour l'égalité des genres", "id":202},
+ {"name":"Centres communautaires NL", "id":204},
+ {"name":"Initiatives de récup' alimentaire", "id":201},
+ {"name":"Soutien aux personnes précaires", "id":193},
+ {"name":"Systèmes d'échange locaux (SEL)", "id":184}
+ ], "color":"#677362"},
+
+ {"id":2, "name":"7. Apprendre, se former", "subcategories":[
+ {"name":"Association d'écologie urbaine", "id":90},
+ {"name":"Espaces de travail partagés, co-working", "id":87},
+ {"name":"Soutien à l'économie locale", "id":12},
+ {"name":"Associations d'éducation permanente", "id":13}
+ ], "color":"#008080"},
+
+ {"id":13, "name":"8. S'exprimer, communiquer", "subcategories":[
+ {"name":"Bornes d'accès à Internet", "id":124},
+ {"name":"Hackerspaces", "id":43},
+ {"name":"Médias indépendants", "id":109},
+ {"name":"Écrivains publics", "id":177},
+ {"name":"Lieux de promotion du logiciel libre", "id":174}
+ ], "color":"#0E408A"},
+
+ {"id":9, "name":"9. Bouger, se déplacer", "subcategories":[
+ {"name":"Cours de vélo", "id":179},
+ {"name":"Ateliers vélo", "id":71},
+ {"name":"Vélos partagés", "id":147},
+ {"name":"Livraisons à vélo", "id":140},
+ {"name":"Pompes à vélos", "id":212},
+ {"name":"Taxis collectifs (Collecto)", "id":196},
+ {"name":"Voitures partagées", "id":141}
+ ], "color":"#0E0140"}
+]
+
+// Our map
+const map = L.map('map')
+const mcg = L.markerClusterGroup({
+ disableClusteringAtZoom: 18,
+ spiderfyOnMaxZoom: false,
+ showCoverageOnHover: false,
+ zoomToBoundsOnClick: true,
+ removeOutsideVisibleBounds: true
+})
+
+// GeoJSON cache of markers by section
+const Markers = []
+const SectionLayers = []
+// Prepare Markers for lazy-loading
+Categories.forEach(function(c) {
+ c.subcategories.forEach(function(s) {
+ Markers[s.id] = {}
+ if (USE_GEOJSON === true) {
+ SectionLayers[s.id] = L.geoJSON([], {
+ filter: function(feature, layer) {
+ console.log('layer filter for ' + s.id)
+ return feature.properties.subcategories[0].id == s.id
+ },
+ onEachFeature: function(feature, layer) {
+ if (feature.properties && feature.properties.popup) {
+ console.log(feature.properties.popup)
+ layer.bindPopup(feature.properties.popup)
+ }
+ }
+ })
+ } else {
+ SectionLayers[s.id] = L.featureGroup.subGroup(mcg, [])
+ }
+ })
+})
+
+// Where do we get our JSON data from
+const api_base = 'http://localhost/data/'
+//const api_base = 'https://map.incommon.cc/data/'
+//const api_base = 'http://maps.dewey.be/api'
+const cat_uri = api_base + '/categories.json'
+
+/**
+ * Setup the navigation to filter markers on map by category and
+ * section. It creates an unordered list of categories to toggle
+ * section display.
+**/
+function navSetup() {
+ var nav = $('body > aside > nav')
+ var ul = $('<ul>')
+ Categories.forEach(function(c, i) {
+ var li = $('<li>')
+ var h3 = $('<h3>')
+ h3.html(c.name)
+ li.append(h3)
+ .attr('id', 'c-' + c.id)
+ .click(function(e) {
+ if ($(this).hasClass('active')) {
+ hideCategoryMarkers(c.id)
+ } else {
+ showActiveCategoryMarkers(c.id)
+ }
+ $(this).toggleClass('active')
+ })
+ .append(listSections(c.subcategories))
+ ul.append(li)
+ })
+ nav.append(ul)
+}
+
+function listSections(sections) {
+ var ul = $('<ul>')
+ sections.forEach(function(s, i) {
+ var li = $('<li>')
+ li.attr('id', 's-' + s.id)
+ li.html(s.name)
+ li.mouseover(function(e) {
+// console.log('mouseover: fetching markers')
+ var sec_id = $(this).attr('id').substr(2)
+ fetchMarkers(sec_id)
+ }).click(function(e) {
+ var sec_id = $(this).attr('id').substr(2)
+ $(this).toggleClass('active')
+ toggleMarkers(sec_id)
+ return false
+ })
+ ul.append(li)
+ })
+ return ul
+}
+
+//
+function toggleMarkers(sec_id) {
+ var section = $('#s-' + sec_id)
+ if (section.hasClass('on-map')) {
+// console.log('remove markers for section ' + sec_id)
+ sectionOffMap(sec_id)
+ } else {
+// console.log('showing markers for section ' + sec_id)
+ sectionOnMap(sec_id)
+ }
+}
+
+/**
+ * Retrieve markers for given section from the JSON API
+ *
+ * @param String sec_id HTML attribute id for the wanted section
+ *
+ * @return Boolean true if remote JSON, false if available locally
+**/
+function fetchMarkers(sec_id) {
+ if ($.isEmptyObject(Markers[sec_id])) {
+ var uri = api_base + 'markers-' + sec_id + '.json'
+ $.getJSON(uri, function(data) {
+ console.log('loaded JSON from ' + uri)
+ }).fail(function() {
+ console.log('failed to get JSON from ' + uri)
+ }).done(function(data) {
+ // Must be in .done() otherwise data is lost
+ Markers[sec_id] = toGeoJSON(data)
+ })
+ return true
+ }
+ return false
+}
+
+/**
+ * Grossly convert incoming JSON objects to GeoJSON
+ *
+ * @param Array of JSON objects with lat and lon coordinates
+ * @return Array of GeoJSON features
+**/
+function toGeoJSON(data) {
+ if (USE_GEOJSON === false) {
+ return data
+ }
+ var myFeatures = $.map(data, function(o, i) {
+ return {
+ "type": "feature",
+ "properties": o,
+ "geometry": {
+ "type": "Point",
+ "coordinates": [o.lat, o.lon]
+ }
+ }
+ })
+ return {
+ "type": "FeatureCollection",
+ "features": myFeatures
+ }
+}
+
+/**
+ * Hide markers in all sections for given category
+ *
+ * @param Integer cat_id The Category ID
+ *
+ * @return Boolean true if any markers were hidden
+ **/
+function hideCategoryMarkers(cat_id) {
+// console.log('hideCategoryMarkers(' + cat_id + ')')
+ Categories.forEach(function(c) {
+ if (c.id == cat_id) {
+ c.subcategories.forEach(function(s) {
+ sectionOffMap(s.id)
+ })
+ return true
+ }
+ })
+ return false
+}
+
+/**
+ * Show markers for all active sections in given category
+ **/
+function showActiveCategoryMarkers(cat_id) {
+// console.log('showActiveCategoryMarkers(' + cat_id + ')')
+ Categories.forEach(function(c) {
+ if (c.id == cat_id) {
+ console.log(cat_id)
+ c.subcategories.forEach(function(s) {
+ var section = $('#s-' + s.id)
+ if (section.hasClass('active')) {
+// console.log(' - showing active section ' + s.id)
+ sectionOnMap(s.id)
+ }
+ })
+ return true
+ }
+ })
+ return false
+}
+
+/**
+ * Display section markers on map
+ **/
+function sectionOnMap(sec_id) {
+// console.log('sectionOnMap(' + sec_id + ')')
+ var section = $('#s-' + sec_id)
+ if (USE_GEOJSON === true) {
+ SectionLayers[sec_id].addData(Markers[sec_id]).addTo(map)
+ } else {
+ var markers = []
+ Markers[sec_id].forEach(function(m) {
+ var layer = markerFor(m)
+ markers.push(layer)
+ })
+ }
+ SectionLayers[sec_id] = L.featureGroup.subGroup(mcg, markers)
+// mcg.addTo(map)
+ SectionLayers[sec_id].addTo(map)
+ section.addClass('on-map')
+}
+
+/**
+ * Hide section markers from map
+ **/
+function sectionOffMap(sec_id) {
+// console.log('sectionOffMap(' + sec_id + ')')
+ var section = $('#s-' + sec_id)
+ if (section.hasClass('on-map')) {
+// console.log('sectionOffMap(' + sec_id + ')')
+ map.removeLayer(SectionLayers[sec_id])
+ section.removeClass('on-map')
+ }
+}
+
+// Careful with icons and MarkerCluster
+// Use iconCreateFunction for styling
+// https://github.com/Leaflet/Leaflet.markercluster/blob/master/example/marker-clustering-custom.html
+// https://github.com/Leaflet/Leaflet/issues/534
+function markerFor(data) {
+ var icon = iconFor(data)
+ var marker = L.marker([data.lat, data.lon], {
+ title: data.name,
+ alt: data.type + ' marker ' + data.id,
+
+ })
+ // Override pre-made popup data
+ data.popup = markerPopupFor(data)
+ marker.bindPopup(data.popup)
+ return marker
+}
+
+const asset_uri = $('script[src$="mapper.js"]' ).attr( 'src' ).replace( 'mapper.js', '' )
+
+function iconFor(data) {
+
+ var cat_id = $('#s-' + data.subcategories[0].id).parents('li').attr('id').substr(2)
+ var color = Categories.find(function(el){ return el.id == cat_id }).color
+// console.log(asset_uri + 'img/')
+
+ var icon = new L.Icon({
+ iconUrl: asset_uri + 'img/marker-' + cat_id + '.png',
+ iconAnchor: new L.Point(16, 16),
+ })
+}
+
+function markerPopupFor(data) {
+ var template = $('#popup-template').html()
+ try {
+ var cat_id = $('#s-' + data.subcategories[0].id).parents('li').attr('id')
+ } catch(e) {
+ var cat_id = 'c-0'
+ }
+ return Mustache.render(template, {
+ marker: data,
+ cat_id: cat_id
+ })
+}
+
+function selected_sections() {
+ var s = [];
+ $('.on-map').each(function() {
+ s.push($(this).attr('id').split('s-')[1]);
+ });
+ return s;
+}
+
+function current_state() {
+ var c = map.getCenter();
+ var z = map.getZoom();
+ var s = selected_sections();
+
+ console.log('current_state: ' + [ c.lat, c.lng, z, s ]);
+
+ return [ c.lat, c.lng, z, s ];
+}
+
+
+/*
+function show_markers(sec_id) {
+ // Create corresponding layer
+ console.log(section_markers[sec_id])
+ section_layers[sec_id] = L.geoJSON(section_markers[sec_id], {
+ filter: function(feature, layer) {
+ return feature.properties.subcategories[0].id == sec_id
+ },
+ onEachFeature: function(feature, layer) {
+ if (feature.properties && feature.properties.popup) {
+ console.log(feature.properties.popup)
+ layer.bindPopup(feature.properties.popup)
+ }
+ }
+ }).addTo(map)
+}
+*/
+
+function locate () {
+ map.locate({ setView: true, maxZoom: 13 })
+ map.on('locationfound', onLocationFound)
+}
+function unlocate () {
+ map.locate({ setView: false, maxZoom: 0 })
+ map.off('locationfound')
+}
+function onLocationFound (e) {
+ var radius = e.accuracy / 2
+
+ if (radius > 1000) {
+ // We must be on a desktop
+ $('#b-locate').remove()
+ return false
+ }
+
+ L.marker(e.latlng).addTo(map)
+ .bindPopup("Vous vous trouvez dans les " + radius + " mètres autour de ce point.").openPopup()
+ L.circle(e.latlng, radius).addTo(map)
+}
+
+
+$(document).ready(function() {
+
+ map.setView([50.83906, 4.35308], 8)
+
+ L.tileLayer('//stamen-tiles-{s}.a.ssl.fastly.net/toner/{z}/{x}/{y}.png', {
+ attribution: 'Map data &copy; <a href="http://openstreetmap.org">OpenStreetMap</a> contributors, <a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>, Imagery © <a href="http://mapbox.com">Mapbox</a>',
+ maxZoom: 18,
+ id: 'c4ptaincrunch.ka5engdh',
+ accessToken: 'pk.eyJ1IjoiYzRwdGFpbmNydW5jaCIsImEiOiJUdWVRSENNIn0.qssi5TBLeBinBsXkZKiI6Q'
+ }).addTo(map)
+
+ // L.control.layers(SectionLayers).addTo(map)
+
+ mcg.addTo(map)
+
+ navSetup()
+
+ var btnLocate = $('<button>')
+ btnLocate
+ .attr('id', 'b-locate')
+ .html('Où suis-je ?')
+ .on('click', locate)
+// $('body>header').append(btnLocate)
+
+ var btnToggle = $('<button>')
+ btnToggle
+ .attr('id', 'b-toggle')
+ .addClass('icon-menu-on')
+ .click(function() {
+ var aside = $('body > aside')
+ if (aside.css('display') != 'none') {
+ btnToggle
+ .removeClass('icon-menu-on').addClass('icon-menu-off')
+ aside.css('display', 'none')
+ $('#map .leaflet-top.leaflet-left').css('left', 0)
+ } else {
+ btnToggle
+ .removeClass('icon-menu-off').addClass('icon-menu-on')
+ $('#map .leaflet-top.leaflet-left').css('left', '20rem')
+ aside.css('display', 'block')
+ }
+ })
+ $('body>header').append(btnToggle)
+
+})