/**
* Wallonie#Demain Prototype
*
* Requires jQuery
**/
// We don't have working GeoJSON support yet
USE_GEOJSON = false
VERSION = '0.0.1'
// 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 = $('
')
Categories.forEach(function(c, i) {
var li = $('- ')
var 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 = $('')
sections.forEach(function(s, i) {
var 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 © OpenStreetMap contributors, CC-BY-SA, Imagery © Mapbox',
maxZoom: 18,
id: 'c4ptaincrunch.ka5engdh',
accessToken: 'pk.eyJ1IjoiYzRwdGFpbmNydW5jaCIsImEiOiJUdWVRSENNIn0.qssi5TBLeBinBsXkZKiI6Q'
}).addTo(map)
// L.control.layers(SectionLayers).addTo(map)
mcg.addTo(map)
navSetup()
var btnLocate = $('