diff --git a/quiz/loadumap.js b/quiz/loadumap.js new file mode 100644 index 0000000..25dacd7 --- /dev/null +++ b/quiz/loadumap.js @@ -0,0 +1,246 @@ +(() => { + var maps = {}; + var layerControl, tileLayers = {}, ovLayers = {}; + var allBounds = {}; + var allMarkers = {}; + var readyEvent = new Event("ready"); + let osm_c = '© OpenStreetMap contributors'; + function load(id, umapFile, options) { + //https://wiki.openstreetmap.org/wiki/Japan/OSMFJ_Tileserver + let mymap = L.map(id).setView([38.891, 139.824], 5); + maps[id] = mymap; + let osmfj = L.tileLayer( + 'https://{s}.tile.openstreetmap.jp/{z}/{x}/{y}.png', + {maxZoom: 20, maxNativeZoom: 18, + attribution: + `OpenStreetMap Foundation Japan | Map Data ${osm_c}` + }).addTo(mymap);; + let osm = L.tileLayer( + '//{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png', { + maxZoom: 20, maxNativeZoom: 18, + attribution: `${osm_c} | HOT` + }) + let cycle = L.tileLayer( + 'https://{s}.tile-cyclosm.openstreetmap.fr/cyclosm/{z}/{x}/{y}.png', + {maxZoom: 20, maxNativeZoom: 18, + attribution: + `CycleOSM | Map Data ${osm_c}`}); + let gsi = L.tileLayer( + '//cyberjapandata.gsi.go.jp/xyz/std/{z}/{x}/{y}.png', { + maxZoom: 20, maxNativeZoom: 18, + attribution: + '国土地理院' + }); + let gsiOrt = L.tileLayer( + '//cyberjapandata.gsi.go.jp/xyz/seamlessphoto/{z}/{x}/{y}.jpg', { + maxZoom: 20, maxNativeZoom: 18, + attribution: + '国土地理院' + }); + tileLayers = {'OpenStreetMap FJ': osmfj, + 'OpenStreetMap HOT': osm, + 'CycleMap': cycle, + '地理院地図': gsi, + '全国最新写真(国土地理院)': gsiOrt}; + L.control.scale().addTo(mymap); + loadumap(id, umapFile, options); + return mymap; + } + function uMapMarkDown(prop) { + let name = prop.name, desc = prop.description, str=""; + if (name) str += `

${name}

\n`; + if (desc) + str += desc.replace(/{{{(.*?)\|(.*?)}}}/g, + ``) + .replace(/{{{(.*?)}}}/g, ``) + .replace(/{{(.*?)\|(.*?)}}/g, ``) + .replace(/{{(.*?)}}/g, ``); + return str; + } + function getHcColor(color) { + //https://zenn.dev/mryhryki/articles/2020-11-12-hatena-background-color + //https://www.w3.org/TR/WCAG20-TECHS/G17.html#G17-tests + //https://note.com/twentynine/n/nd79c8dd275d9 + let elm = document.createElement("p") + elm.style.color = color; + document.body.appendChild(elm); + let comp=getComputedStyle(elm).getPropertyValue("color"), + c = comp.match(/\d+/g), + r = c[0]/255, g=c[1]/255, b=c[2]/255, + lum = 0.2126*r + 0.7152 * g + 0.0722 * b; + elm.remove(); + let white = (1+0.05)/(lum+0.05), + black = (lum+0.05)/(0+0.05); + //console.log(`color=${color}, c=${c}, lum=${lum}, white=${white}, black=${black}`) + return (white>black) ? "white" : "black"; + } + function divIconProps(oldProps) { + //https://upload.wikimedia.org/wikipedia/commons/ + let iconClass = oldProps.iconClass || "Default", + color = oldProps.color || "navy"; + let div1="", div2="", style1, style2, iconAnchor, popupAnchor; + let iconURL = oldProps.iconUrl, + iconBase = "https://umap.openstreetmap.fr"; + // iconBase = "img/icons" + let defIcon = "●"; + let fgColor = getHcColor(color), //Which offers high contrast, B or W? + inv = (fgColor=="white" ? 1 : 0); + if (iconURL) { + if (iconURL.match(/(jpe?g|png|svg)/i)) + defIcon = `` + else + defIcon = iconURL; + } + switch (iconClass) { + case "Ball": + style1 = `background: transparent radial-gradient(circle at 20% 40%, white, ${color} 80%, transparent 90%`; + style2 = `background-color: red;`; + iconAnchor = [5, 28]; + popupAnchor = [10, -24]; + break; + case "Drop": + div1=defIcon; + style1 = `color: ${fgColor}; border-color: yellow; background: ${color};`; + style2 = `border-top-color: ${color};`; + iconAnchor = [16, 48]; + popupAnchor = [0, -48]; + break; + case "Default": case undefined: + div1=defIcon; + style1 = `color: ${fgColor}; border-color: yellow; background: ${color};`; + style2 = `border-top-color: ${color};`; + iconAnchor = [16, 48]; + popupAnchor = [0, -48]; + break; + } + return { + html: `
${div1}
\n` + + `
${div2}
`, + iconAnchor: iconAnchor, + popupAnchor: popupAnchor, + className: iconClass, + }; + } + function uMapBuild(id, umapobj, options) { + //options: + // onEachFeature: function(feature, layer) /* same as L.geojson */ + // selectLayers: [layer1, layer2, ...] /* Show only those layers */ + // layer1,... can be index number of layers + // selectLayers: /RegExp/ is also acceptable + if (umapobj.type != "umap") return; + let mymap = maps[id], + mymapElm = document.getElementById(id), + callback = options.onEachFeature, + selection = options.selectLayers; + allBounds[id] = []; + allMarkers[id] = []; + for (let i in umapobj.layers) { + // console.log(typeof i); /* Care: i is string!! */ + var readyCount = 0; + let layer = umapobj.layers[i], + lp = (layer._umap_options||layer._storage), + layername = lp.name; + if (selection) { + if (selection.indexOf) { // Specified by array + if (selection.indexOf(parseInt(i))==-1 + && selection.indexOf(layername)==-1) + continue; + } else if (selection.exec) { // by RegExp + if (!selection.exec(layername)) + continue; + } + } + let defPopup = (layer._umap_options && layer._umap_options.name) + || (layer._storage && layer._storage.name) + ; + var count = 0; + // To use hook when geoJson Layer is ready, + // set "layeradd" event handler before addData to geoJson layer: + // https://github.com/Leaflet/Leaflet/issues/4609 + let gjl = L.geoJson(null, { + onEachFeature: (f, l)=>{ + let fprop = f.properties, + umapopt = (fprop&&fprop._umap_options); + if (l.getBounds) { + allBounds[id].push(l.getBounds()); + } else if (l.getLatLng) { + allBounds[id].push([l.getLatLng().lat, l.getLatLng().lng]); + } + if (fprop) { + let link = (umapopt && umapopt.outlink); + if (fprop.name||fprop.description) { + l.bindPopup(uMapMarkDown(fprop)); + } else if (defPopup) { + // If no "name" property set, set Layer name + l.bindPopup(defPopup); + } + if (link) + l.on("click", ()=>{window.open(link, "_blank");}); + } + if (typeof callback == 'function') + callback(f, l); + if (++count == layer.features.length) { + // If this is the last record, call fitBounds() + //mymap.fitBounds(L.latLngBounds(allBounds[id])); + } + }, + style: (f)=> { + if (f.properties && f.properties._umap_options) { + return f.properties._umap_options; + } + }, + pointToLayer: (f, latlng)=> { + let prop = f.properties._umap_options || {}; + var m = L.marker(latlng); + allMarkers[id].push(m); + m.setIcon(x=L.divIcon(divIconProps(prop))); + return m; + } + }).on("layeradd", ()=>{ + if (layer.features.length && layer.features.length == ++readyCount) { + mymap.fitBounds(L.latLngBounds(allBounds[id])); + mymapElm.dispatchEvent(readyEvent); + } + console.log(`readyCount=${readyCount}, count=${count}`); + }).addTo(mymap); + gjl.addData(layer); + if (layername) ovLayers[lp.name] = gjl; + } + // Add custom tileLayer, if any. + if (umapobj.properties && umapobj.properties.tilelayer) { + let tl = umapobj.properties.tilelayer; + if (tl.url_template) { + tileLayers[tl.name] = L.tileLayer( + tl.url_template, + tl // tl itself is a form of property list + )//.addTo(mymap); + } + } + layerControl = L.control.layers(tileLayers, ovLayers); + if (!options.inhibitLayerControl) + layerControl.addTo(mymap); + } + function loadumap(id, umapfile, options) { + fetch(umapfile).then((resp)=>{ + return resp.json(); + }).then((json)=>{ + try { + uMapBuild(id, json, options); + } catch (e) { + alert(`Parsing error while reading ${umapfile}.`); + return; + } + }) + } + + + let me = {}; + me.load = load; + me.maps = maps; + me.allBounds = allBounds; + me.allMarkers = allMarkers; + me.tileLayers = tileLayers; + me.layerControl = layerControl; + me.ovLayers = ovLayers + window.Lumap = me; +})();