Implementation du systmème de symbole sur l'app web

This commit is contained in:
EpicKiwi 2026-05-29 20:41:54 +02:00
parent ef1734ccd4
commit f7e5bb37bf
Signed by: epickiwi
GPG key ID: C4B28FD2729941CE
15 changed files with 599 additions and 190 deletions

117
js/map.js
View file

@ -1,5 +1,5 @@
import * as L from "./lib/leaflet/leaflet-src.esm.js"
import { PlaceDatabase } from "./places.js";
import { MapSubFeature, PlaceDatabase } from "./places.js";
import "./lib/turf/turf.js"
let map = null;
@ -93,93 +93,43 @@ export async function init(){
const HIGHLIGHT_LAYER = Symbol("Highlight map layer")
const AMENITY_MARKER_LAYER = Symbol("Amenity marker layer")
const PARENT_FEATURE = Symbol("Parent feature")
const HIGHLIGHT_MARKER_ICON = L.divIcon({
className: "highlight-point-icon"
})
export async function init_places(places_db){
places = places_db
// La couche des zones disponibles
const amenities = L.geoJSON(null, {
pointToLayer: function(feature, latlng) {
let iconEl = document.createElement("div")
iconEl.classList.add("map-amenity-icon-container")
if(feature.properties["eau potable"]){
let drinking_water_icon = document.createElement("img")
drinking_water_icon.src = new URL("../icons/eau-potable.svg", import.meta.url).toString()
drinking_water_icon.alt = "Icone d'une goute"
drinking_water_icon.title = "Eau potable"
drinking_water_icon.classList.add("drinking-water")
iconEl.append(drinking_water_icon)
}
if(feature.properties["toilettes"]){
let bathrooms_icon = document.createElement("img")
bathrooms_icon.src = new URL("../icons/toilettes.svg", import.meta.url).toString()
bathrooms_icon.alt = "Icone de toilettes"
bathrooms_icon.title = "Toilettes"
bathrooms_icon.classList.add("bathroom")
iconEl.append(bathrooms_icon)
}
if(feature.properties["douches"]){
let shower_icon = document.createElement("img")
shower_icon.src = new URL("../icons/douches.svg", import.meta.url).toString()
shower_icon.alt = "Icone de douche"
shower_icon.title = "Douches"
shower_icon.classList.add("shower")
iconEl.append(shower_icon)
}
if(feature.properties["n couchage"]){
let dorm_icon = document.createElement("img")
dorm_icon.src = new URL("../icons/dortoir.svg", import.meta.url).toString()
dorm_icon.alt = "Icone de dortoir"
dorm_icon.title = "Dortoir"
dorm_icon.classList.add("dorm")
iconEl.append(dorm_icon)
}
let icon = L.divIcon({
className: "map-amenity-icon",
html: iconEl
})
return L.marker(latlng, {
icon: icon
})
},
onEachFeature: function(feature, layer){
if(feature[PARENT_FEATURE]){
feature[PARENT_FEATURE][AMENITY_MARKER_LAYER] = layer
} else {
feature[AMENITY_MARKER_LAYER] = layer
}
}
}).addTo(map)
// La couche des zones disponibles
const area_highlight = L.geoJSON(null, {
pointToLayer: function(feature, latlng) {
return L.marker(latlng, {
icon: HIGHLIGHT_MARKER_ICON
})
let symbol = feature.mapSymbol;
if(symbol.markerUrl) {
let iconEl = document.createElement("img");
iconEl.src = symbol.markerUrl;
if(iconEl){
return L.marker(latlng, {
icon: L.divIcon({
className: "highlight-point-icon",
html: iconEl,
iconSize: [0, 0]
})
})
}
}
},
style: function(feature){
let symbol = feature.mapSymbol;
return {
className: "map-hilight-area",
fill: false,
stroke: false
fill: !!symbol.backgroundColor,
fillColor: symbol.backgroundColor,
fillOpacity: 0.5,
stroke: !!symbol.borderColor,
color: symbol.borderColor,
}
},
onEachFeature: function(feature, layer){
if(feature[PARENT_FEATURE]){
feature[PARENT_FEATURE][HIGHLIGHT_LAYER] = layer
} else {
feature[HIGHLIGHT_LAYER] = layer
}
feature[HIGHLIGHT_LAYER] = layer
}
}).addTo(map)
@ -187,22 +137,9 @@ export async function init_places(places_db){
for(let [_feature_id, feature] of Object.entries(places.featuresById)){
area_highlight.addData(feature)
if(feature.properties["toilettes"]
|| feature.properties["douches"]
|| feature.properties["eau potable"]
|| feature.properties["n couchage"]
) {
let amenity_feature
if(feature.geometry.type != "Point"){
amenity_feature = turf.centerOfMass(feature)
amenity_feature[PARENT_FEATURE] = feature
amenity_feature.properties = feature.properties
} else {
amenity_feature = feature
}
amenities.addData(amenity_feature)
let point_feature = feature.asPoint()
if(point_feature !== feature){
area_highlight.addData(point_feature)
}
}
}

View file

@ -1,10 +1,11 @@
import lunr from "./lib/lunr/lunr.js"
import lunrStemmer from "./lib/lunr/lunr.stemmer.support.js"
import lunrFr from "./lib/lunr/lunr.fr.js"
import { MapSymbol } from "./symbols.js"
lunrStemmer(lunr)
lunrFr(lunr)
const FEATURE_I = Symbol("Feature index")
const FEATURE_INDEX = Symbol("Feature index")
export const FEATURE_ID = Symbol("Feature id")
/**
@ -92,22 +93,29 @@ export class PlaceDatabase extends EventTarget {
let all_ids = []
if(geojson.type == "Feature"){
let featureId = geojson?.properties?.id || geojson?.properties?.[FEATURE_I]
let full_id = idPrefix+"#"+featureId
if(this.featuresById[full_id]){
console.warn(`Warning: a feature with ID "${full_id}" already exists in database`)
let feature = geojson;
if(!(feature instanceof MapFeature)){
feature = new MapFeature(geojson)
}
geojson[FEATURE_ID] = full_id
this.featuresById[full_id] = geojson
if(!feature.id){
let full_id = idPrefix+"#"+(geojson?.properties?.id || geojson?.properties?.[FEATURE_INDEX])
feature.id = full_id
}
if(this.featuresById[feature.id]){
console.warn(`Warning: a feature with ID "${feature.id}" already exists in database`)
}
this.featuresById[feature.id] = feature
let event = new Event("newfeature")
event.ref = full_id
event.feature = geojson
event.ref = feature.id
event.feature = feature
this.dispatchEvent(event)
all_ids.push(full_id)
all_ids.push(feature.id)
} else if(geojson.type == "FeatureCollection") {
@ -115,15 +123,15 @@ export class PlaceDatabase extends EventTarget {
idPrefix += (idPrefix != "" ? "-" : "")+geojson.name
}
if(geojson?.properties?.[FEATURE_I]){
idPrefix += (idPrefix != "" ? "-" : "")+geojson.properties[FEATURE_I]
if(geojson?.properties?.[FEATURE_INDEX]){
idPrefix += (idPrefix != "" ? "-" : "")+geojson.properties[FEATURE_INDEX]
}
for(let [i, feature] of Object.entries(geojson.features)){
feature.properties = {
...(feature.properties || {}),
[FEATURE_I]: i
[FEATURE_INDEX]: i
}
let id = this.addFeature(feature, {
@ -159,19 +167,21 @@ export class PlaceDatabase extends EventTarget {
this.field("synonyms")
for(let [id, feature] of Object.entries(database.featuresById)){
let synonyms = ""
let sym = feature.mapSymbol;
if(feature.properties["n couchage"] > 0){
synonyms += SLEEPING_SYNONYMS.join(" ")
let synonyms = []
if(sym.genericName){
synonyms.push(sym.genericName)
}
if(feature.properties["toilettes"]){
synonyms += " "+TOILETTES_SYNONYMS.join(" ")
if(sym.indexSynonyms){
synonyms.push(...sym.indexSynonyms)
}
this.add({
id,
name: feature.properties.name,
name: feature.properties.name || sym.genericName,
synonyms
})
}
@ -213,32 +223,51 @@ export class PlaceDatabase extends EventTarget {
}
const SLEEPING_SYNONYMS = [
"dodo",
"dortoir",
"mimir",
"dormir",
"lit"
]
const POINT_FEATURE = Symbol("Point feature")
const TOILETTES_SYNONYMS = [
"caca",
"pipi",
"cabinets",
"water-closet",
"latrines",
"🚽",
"double véssé",
"chaise percée",
"WC",
"waters",
"toilettes",
"toilette",
"chiotte",
"chiottes",
"le trône",
"le trones",
"pièce mystère",
"backroom",
"retailleau"
]
export class MapFeature {
constructor(geojson){
Object.assign(this, geojson)
}
get id(){
return this[FEATURE_ID]
}
set id(value){
this[FEATURE_ID] = value
}
get mapSymbol(){
return MapSymbol.fromFeature(this)
}
asPoint(){
if(!this[POINT_FEATURE]){
if(!this.geometry){
debugger;
}
if(this.geometry.type != "Point"){
let point_feature = turf.centerOfMass(this)
point_feature.properties = this.properties
point_feature = new MapSubFeature(point_feature, this)
this[POINT_FEATURE] = point_feature
} else {
this[POINT_FEATURE] = this
}
}
return this[POINT_FEATURE]
}
}
const PARENT_FEATURE = Symbol("Parent feature")
export class MapSubFeature extends MapFeature {
constructor(geojson, parent){
super(geojson)
this[PARENT_FEATURE] = parent
}
get parentFeature(){
return this[PARENT_FEATURE]
}
}

204
js/symbols.js Normal file
View file

@ -0,0 +1,204 @@
import { MapFeature } from "./places.js"
export class MapSymbol {
/**
* Solid background color of this area (if any)
* @type {undefined|string}
*/
backgroundColor = undefined
/**
* Background raster tiled image to put on top of solid background (if any)
* @type {undefined|string|URL}
*/
backgroundUrl = undefined
/**
* Border color of area (if any)
* @type {undefined|string}
*/
borderColor = undefined
/**
* Marker URL to place on feature (if point) or on centroid (if area)
* @type {undefined|string|URL}
*/
markerUrl = undefined
/**
* Generic name of this feature if no more precise name is given
* @type {undefined|string}
*/
genericName = undefined
/**
* A list of sysnonyms for this feature for full text indexing
* @type {string[]}
*/
indexSynonyms = []
clone(){
let new_symb = new MapSymbol()
Object.assign(new_symb, this)
return new_symb
}
static fromFeature(feature){
return getSymbolForFeature(feature)
}
}
/**
* Get a map symbol from feature properties
* @param {MapFeature|Object} feature
*/
function getSymbolForFeature(feature){
let sym = getBaseSymbolForFeature(feature);
if(feature.properties["color"]){
sym = sym.clone()
sym.backgroundColor = feature.properties["color"]
sym.borderColor = feature.properties["color"]
}
return sym
}
function getBaseSymbolForFeature(feature){
if(feature.properties["wifi"]){
return WIFI_SYMBOL
}
if(feature.properties["zone-interdite"]){
return VERBOTEN_AREA_SYMBOL
}
if(feature.properties["piscine"]){
return SWIMMING_POOL_SYMBOL
}
if(feature.properties["Parking"]){
return PARKING_SYMBOL
}
if(feature.properties["n couchage"] > 0){
return SLEEPING_SYMBOL
}
if(feature.properties["toilettes"]){
return TOILETS_SYMBOL
}
if(feature.properties["douches"]){
return SHOWER_SYMBOL
}
if(feature.properties["eau potable"]){
return DRINKING_WATER_SYMBOL
}
if(feature.properties["batiment"] || feature.properties["piece-batiment"]){
return BUILDING_SYMBOL
}
return DEFAULT_SYMBOL
}
// PREDEFINED Symbols
export const DEFAULT_SYMBOL = new MapSymbol()
{
DEFAULT_SYMBOL.markerUrl = new URL("../icons/default-marker.svg", import.meta.url)
DEFAULT_SYMBOL.backgroundColor = "white"
DEFAULT_SYMBOL.borderColor = "white"
}
export const SLEEPING_SYMBOL = new MapSymbol()
{
SLEEPING_SYMBOL.markerUrl = new URL("../icons/dortoir.svg", import.meta.url)
SLEEPING_SYMBOL.genericName = "Dortoir"
SLEEPING_SYMBOL.indexSynonyms = [
"dodo",
"dortoir",
"mimir",
"dormir",
"lit"
]
}
export const DRINKING_WATER_SYMBOL = new MapSymbol()
{
DRINKING_WATER_SYMBOL.markerUrl = new URL("../icons/eau-potable.svg", import.meta.url)
DRINKING_WATER_SYMBOL.genericName = "Point d'eau potable"
}
export const PARKING_SYMBOL = new MapSymbol()
{
PARKING_SYMBOL.markerUrl = new URL("../icons/parking.svg", import.meta.url)
PARKING_SYMBOL.genericName = "Parking"
PARKING_SYMBOL.backgroundColor = "#2d2d2d"
PARKING_SYMBOL.borderColor = "#989898"
}
export const TOILETS_SYMBOL = new MapSymbol()
{
TOILETS_SYMBOL.markerUrl = new URL("../icons/toilettes.svg", import.meta.url)
TOILETS_SYMBOL.genericName = "Toilettes"
TOILETS_SYMBOL.indexSynonyms = [
"caca",
"pipi",
"cabinets",
"water-closet",
"latrines",
"🚽",
"double véssé",
"chaise percée",
"WC",
"waters",
"toilettes",
"toilette",
"chiotte",
"chiottes",
"le trône",
"le trones",
"pièce mystère",
"backroom",
"retailleau"
]
}
export const SHOWER_SYMBOL = new MapSymbol()
{
SHOWER_SYMBOL.markerUrl = new URL("../icons/douches.svg", import.meta.url)
SHOWER_SYMBOL.genericName = "Douche"
}
export const SWIMMING_POOL_SYMBOL = new MapSymbol()
{
SWIMMING_POOL_SYMBOL.markerUrl = new URL("../icons/piscine.svg", import.meta.url)
SWIMMING_POOL_SYMBOL.genericName = "Piscine"
SWIMMING_POOL_SYMBOL.backgroundColor = "#366d73"
SWIMMING_POOL_SYMBOL.borderColor = "#366d73"
}
export const VERBOTEN_AREA_SYMBOL = new MapSymbol()
{
VERBOTEN_AREA_SYMBOL.genericName = "Zone interdite"
VERBOTEN_AREA_SYMBOL.backgroundColor = "#992700"
VERBOTEN_AREA_SYMBOL.borderColor = "#992700"
}
export const BUILDING_SYMBOL = new MapSymbol()
{
BUILDING_SYMBOL.genericName = "Batiment"
BUILDING_SYMBOL.backgroundColor = "#5a5a5a"
BUILDING_SYMBOL.borderColor = "white"
}
export const WIFI_SYMBOL = new MapSymbol()
{
WIFI_SYMBOL.genericName = "Wifi"
WIFI_SYMBOL.markerUrl = new URL("../icons/wifi.svg", import.meta.url)
}