diff options
Diffstat (limited to 'assets/mapper.js')
-rw-r--r-- | assets/mapper.js | 485 |
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 © <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) + +}) |