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;
+})();