- // ==UserScript==
- // @name Feature Your Map
- // @version 2.4.2
- // @description get poi data from osm
- // @author KaKa
- // @match https://map-making.app/maps/*
- // @grant GM_setClipboard
- // @license MIT
- // @icon https://www.google.com/s2/favicons?domain=geoguessr.com
- // @require https://cdn.jsdelivr.net/npm/sweetalert2@11
- // @namespace http://tampermonkey.net/
- // ==/UserScript==
-
- (function() {
- 'use strict';
- document.addEventListener('DOMContentLoaded', function() {
- document.addEventListener('change', function(event) {
- const checkbox = event.target;
- if (checkbox.type === 'checkbox') {
- const label = checkbox.parentElement;
- if (checkbox.checked) {
- label.classList.add('checked');
- } else {
- label.classList.remove('checked');
- }
- }
- });
- });
-
- let globalSettings = {
- selectedFeature: null,
- isIncluded: null,
- radius: null
- };
-
- let mapFeatures={'way':['motorway','trunk','primary','secondary','tertiary','unclassified','footway','path','pedestrain','river','bridge','tunnel','roundabout','coastline'],
- 'node':[ 'bus stop', 'level crossing','milestone', 'crosswalk','traffic light','postbox', 'hydrant','utility pole', 'lamppost','waste basket',
- 'waste disposal','yield sign','stop sign','stadium','museum','school','motorway junction','tree','volcano','cape','hill'],
- 'relation':['grassland','forest','residental','farmland','meadow','paddy','vineyard'],
- 'nw':['bollard','hospital','train station','religious','government','hotel','estate agent']}
-
- let taglist = [['estate agent','"shop"="estate_agent"'],
- ['coastline','"natural"="coastline"'],
- ['bollard','"barrier"="bollard"'],
- ['vineyard','"landuse"="vineyard"'],
- ['paddy','"landuse"="paddy"'],
- ['meadow','"landuse"="meadow"'],
- ['residental','"landuse"="residental"'],
- ['farmland','"landuse"="farmland"'],
- ['hill','"natural"="hill"'],
- ['volcano', '"natural"="volcano"'],
- ['grassland','"natural"="grassland"'],
- ['forest','"natural"="wood"'],
- ['cape', '"natural"="cape"'] ,
- ['tree', '"natural"="tree"'],
- ['bridge', '"bridge"="yes"'],
- ['bus stop', '"highway"="bus_stop"'],
- ['utility pole', '"power"="pole"'],
- ['traffic light', '"highway"="traffic_signals"'],
- ['lamppost', '"highway"="street_lamp"'],
- ['crosswalk', '"highway"="crossing"'],
- ['level crossing', '"railway"="level_crossing"'],
- ['postbox', '"amenity"="post_box"'],
- ['hydrant', '"emergency"="fire_hydrant"'],
- ['milestone', '"highway"="milestone"'],
- ['motorway','"highway"="motorway"'],
- ['trunk','"highway"="trunk"'],
- ['primary','"highway"="primary"'],
- ['secondary','"highway"="secondary"'],
- ['tertiary','"highway"="tertiary"'],
- ['unclassified','"highway"="unclassified"'],
- ['footway','"highway"="footway"'],
- ['path','"highway"="path"'],
- ['pedestrain','"highway"="pedestrain"'],
- ['river','"waterway"="river"'],
- ['railway','"railway"="rail"'],
- ['tram','"railway"="tram"'],
- ['tunnel','"tunnel"="yes"'],
- ['yield sign','"highway"="give_way"'],
- ['roundabout','"junction"="roundabout"'],
- ['waste basket','"amenity"="waste_basket"'],
- ['waste disposal','"amenity"="waste_disposal"'],
- ['hospital','"amenity"="hospital"'],
- ['government','"building"="government"'],
- ['religious','"amenity"="place_of_worship"'],
- ['stop sign','"highway"="stop"'],
- ['museum','"building"="museum"'],
- ['train station','"building"="train_station"'],
- ['stadium','"leisure"="stadium"'],
- ['school','"amenity"="school"'],
- ['hotel','"building"="hotel"'],
- ['motorway junction','"highway"="motorway_junction"']
- ];
-
- let categories = {
- 'Traffic': ['bridge', 'roundabout','tunnel', 'level crossing','bollard','milestone', 'crosswalk','yield sign','stop sign','motorway junction'],
- 'Public Facility': ['bus stop','postbox', 'hydrant','utility pole', 'lamppost','traffic light','waste basket','waste disposal'],
- 'Building':['government','school','hospital','stadium','museum','religious','hotel','estate agent'],
- 'Natural':['volcano','tree','cape','hill']};
-
- let advancedCategories={'Intersection':['motorway','trunk','primary','secondary','tertiary','unclassified','footway','path','pedestrain'],
- 'Around Search':['bridge', 'bus stop', 'level crossing','milestone', 'crosswalk','traffic light','postbox', 'hydrant','utility pole',
- 'lamppost','river','government','school','hospital','waste basket','waste disposal','stadium','museum','religious',
- 'tunnel','roundabout','hotel','motorway junction','tree','volcano','cape','coastline','hill','forest','grassland',
- 'residental','farmland','meadow','paddy','vineyard']}
-
- const API_URL = "https://overpass-api.de/api/interpreter";
- const checkboxButtonStyle = `.checkbox-button {
- display: inline-block;
- cursor: pointer;
- background-color: #007bff;
- color: #fff;
- padding: 5px 10px;
- border-radius: 5px;
- margin-right: 10px;}
-
- .checkbox-button:hover {
- background-color: #4CAF50;
- border-color: #4CAF50;}
-
- .checkbox-button input[type="checkbox"] {
- display: none;}
-
- .checkbox-button.checked {
- background-color: #4CAF50;
- color: #fff;
- font-weight: bold;
- border-color: #4CAF50;}
-
- .category-item {
- display: flex;
- flex-direction: column;
- align-items: flex-start;}
-
- .category-row {
- display: flex;
- justify-content: space-between;
- margin-bottom: 20px;
- margin-right: 30px;
- margin-left: 30px;}
-
- .flex-fill {
- flex: 1;}`;
-
- async function fetchData(query, mode,feature,advanced) {
- const requestBody = getRequestBody(feature,mode,advanced,query)
- const response = await fetch(API_URL, {
- method: "POST",
- body: "data=" + encodeURIComponent(requestBody),
- });
-
- if (!response.ok) {
- throw new Error("Network response was not ok");
- }
-
- return response.json();
- }
-
- async function getData(query, mode,features,advanced) {
- try {
- const swal = Swal.fire({
- title: 'Fetching Coordinates',
- text: 'Please wait...',
- allowOutsideClick: false,
- allowEscapeKey: false,
- showConfirmButton: false,
- didOpen: () => {
- Swal.showLoading();
- }
- });
- const js = {
- "name": "",
- "customCoordinates": [],
- "extra": {
- "tags": {},
- "infoCoordinates": []
- }
- };
- let elements = [];
- if (advanced==='Intersection'){
- const response=await fetchData(query, mode, features,advanced)
- if (response.remark && response.remark.includes("runtime error")) {
- alert("RAM runned out or query timed out. Please try narrowing your search scope.");
-
- } else if (response.elements && response.elements.length > 0) {
- elements.push(...response.elements);
- }
- writeData(elements, features, js,advanced);
- }
-
- else if(advanced==='Around'){
- const response=await fetchData(query, mode, features,advanced)
- if (response.remark && response.remark.includes("runtime error")) {
- alert("RAM runned out or query timed out. Please try narrowing your search scope.");
-
- } else if (response.elements && response.elements.length > 0) {
- elements.push(...response.elements);
- }
- writeData(elements, features, js,advanced)}
- else{
- for (let feature of features) {
- let requests = [];
-
- requests.push(fetchData(query, mode, feature));
-
- const responses = await Promise.all(requests);
-
- responses.forEach(response => {if (response.remark && response.remark.includes("runtime error")) {
- alert("RAM runned out or query timed out. Please try narrowing your search scope.");
- } else
- if (response.elements && response.elements.length > 0) {
- elements.push(...response.elements);
- }
- });
- writeData(elements, feature[0], js);
-
- }
- }
- if (js.customCoordinates.length === 0) {
- swal.close()
- if (mode === 'area') {
- Swal.fire('Error',"None retrived!The place name you entered may be incorrect,couldn't find this place.",'error');
- } else if (mode === 'polygon') {
- Swal.fire('Error',"None retrived!Please check if your geojson file format is correct.",'error');
- }
- else{
- Swal.fire('Error',"None retrived!Please narrow the radius or select a less features combination.",'error')}
- }
- if (js.customCoordinates.length > 0) {
- swal.close()
- GM_setClipboard(JSON.stringify(js));
- Swal.fire('Success',"JSON data has been copied to your clipboard!",'success');
- }
- } catch (error) {
- Swal.fire('Error',`Error fetching data${error}:`,'error');
- }
- }
-
- function getFeatureElement(f){
- for (const key in mapFeatures) {
- if (mapFeatures.hasOwnProperty(key)) {
- if (mapFeatures[key].includes(f)) {
- return key
- }}}}
-
- function getCategoryHtml(categories) {
- let html = '';
- for (let category in categories) {
- html += `<input type="radio" name="category" value="${category}" id="swal-input-${category}">
- <label for="swal-input-${category}">${category}</label><br>`;
- }
- return html;
- }
-
- function getSettingFeaturesHtml(features) {
- let html = '';
- for (let feature of features) {
- html += `<input type="radio" name="feature" value="${feature}" id="swal-input-${feature}">
- <label for="swal-input-${feature}">${feature}</label><br>`;
- }
- return html;
- }
-
- async function getSettings() {
- const resetSettings = () => {
- globalSettings.selectedFeature = null;
- globalSettings.isIncluded = null;
- globalSettings.radius = null;
- };
-
- const setSettings = (feature, isIncluded, radius) => {
- globalSettings.selectedFeature = feature;
- globalSettings.isIncluded = isIncluded;
- globalSettings.radius = radius;
- };
-
- resetSettings();
- let settingCategories = {
- 'Transportation': ['bridge', 'roundabout', 'tunnel', 'level crossing', 'milestone', 'crosswalk', 'yield sign', 'stop sign', 'motorway junction'],
- 'Public Facility': ['bus stop', 'postbox', 'hydrant', 'utility pole', 'lamppost', 'traffic light', 'waste basket', 'waste disposal'],
- 'Building': ['government', 'school', 'hospital', 'stadium', 'museum', 'religious', 'hotel'],
- 'Natural': ['volcano', 'tree', 'cape', 'hill', 'forest', 'grassland', 'coastline'],
- 'Landuse': ['farmland', 'paddy', 'meadow', 'vineyard', 'residental']
- };
-
- let selectedSettingCategory = null;
-
- const { value: selectedCategory, dismiss: initializeSettings } = await Swal.fire({
- title: 'Setting Categories',
- html: getCategoryHtml(settingCategories),
- focusConfirm: false,
- allowOutsideClick: false,
- showCancelButton: true,
- showCloseButton: true,
- cancelButtonText: 'Reset Settings',
- preConfirm: () => {
- const selectedCategoryRadio = document.querySelector('input[name="category"]:checked');
- if (!selectedCategoryRadio) {
- Swal.showValidationMessage('Please select a category');
- return;
- }
- selectedSettingCategory = selectedCategoryRadio.value;
- return selectedSettingCategory;
- }
- });
-
- if (initializeSettings=='cancel') {
- Swal.fire({
- icon: 'success',
- title: 'Settings Reset',
- text: 'Your settings have been successfully reset.',
- showConfirmButton: false,
- timer: 1500
- });
- resetSettings();
-
- }
-
- if (selectedCategory) {
- let selectedSettingFeatures = settingCategories[selectedSettingCategory];
-
- const { value: selectedFeature, dismiss: cancelInput } = await Swal.fire({
- title: 'Setting Features',
- html: getSettingFeaturesHtml(selectedSettingFeatures) + '<a href="https://wiki.openstreetmap.org/wiki/Map_features" target="_blank" style="color: black; font-size: 16px;">More information about features...</a>',
- focusConfirm: false,
- allowOutsideClick: false,
- showCancelButton: true,
- preConfirm: () => {
- const selectedFeatureRadio = document.querySelector('input[name="feature"]:checked');
- if (!selectedFeatureRadio) {
- Swal.showValidationMessage('Please select a feature');
- return;
- }
- return selectedFeatureRadio.value;
- }
- });
-
- if (selectedFeature) {
- const { value: isIncluded, dismiss: cancelInclude } = await Swal.fire({
- title: 'Select POI Range',
- text: 'Do you want to include POIs within a certain range or outside of it?',
- icon: 'question',
- showCancelButton: true,
- confirmButtonText: 'Within Range',
- cancelButtonText: 'Outside Range',
- allowOutsideClick:false,
- });
-
- let radius = 50;
- if (isIncluded !== Swal.DismissReason.cancel) {
- const { value: inputRadius, dismiss: cancelRadius } = await Swal.fire({
- title: 'Enter Radius',
- input: 'number',
- inputLabel: 'Radius (in meters)',
- inputPlaceholder: 'Enter the radius for POIs',
- inputAttributes: {
- min: 5,
- max: 10000,
- step: 100,
- },
- showCancelButton: true,
- confirmButtonText: 'OK',
- cancelButtonText: 'Cancel',
- allowOutsideClick:false,
- inputValidator: (value) => {
- if (!value) {
- return 'You need to enter a radius!';
- }
- const radiusValue = parseInt(value);
- if (radiusValue < 5 || radiusValue > 10000) {
- return 'Radius must be between 5 and 10000 meters!';
- }
- }
- });
-
- if (inputRadius !== undefined && inputRadius !== null) {
- radius = parseInt(inputRadius);
- }
- }
-
- if (selectedFeature && isIncluded !== Swal.DismissReason.cancel) {
- const filteredTags = taglist.filter(tag => selectedFeature.includes(tag[0]));
- setSettings(filteredTags[0], isIncluded, radius);
- Swal.fire({
- icon: 'success',
- title: 'Settings Updated',
- text: 'Your settings have been successfully updated.',
- showConfirmButton: false,
- timer: 1200
- });
- }
- }
- }
- }
-
- function getRequestBody(features, mode, advanced, query) {
- let requestBody = "";
- var selectedFeatures=globalSettings.selectedFeature
- const outJsonTimeout = "[out:json][timeout:180];";
- if (advanced === "Intersection") {
- if (globalSettings.selectedFeature){
- if (globalSettings.isIncluded){
- requestBody = `${outJsonTimeout}${getFeatureElement(selectedFeatures[0])}[${selectedFeatures[1]}](poly:"${query}");
- way(poly:"${query}")[highway~"^(${features[0].join('|')})$"]->.w1;
- way(poly:"${query}")[highway~"^(${features[1].join('|')})$"]->.w2;
- node(w.w1)(w.w2)(around:${globalSettings.radius});
- out geom;`;}
- else {
- requestBody = `${outJsonTimeout}(${getFeatureElement(selectedFeatures[0])}[${selectedFeatures[1]}](poly:"${query}");)->.default;
- way(poly:"${query}")[highway~"^(${features[0].join('|')})$"]->.w1;
- way(poly:"${query}")[highway~"^(${features[1].join('|')})$"]->.w2;
- node(w.w1)(w.w2)->.all;
- (node.all(around.default:${globalSettings.radius});)->.inner;
- (.all; - .inner;);
- out geom meta;`
- }
- }
- else {
- requestBody = `${outJsonTimeout}
- way(poly:"${query}")[highway~"^(${features[0].join('|')})$"]->.w1;
- way(poly:"${query}")[highway~"^(${features[1].join('|')})$"]->.w2;
- node(w.w1)(w.w2);
- out geom;`;}
- } else if (advanced === "Around") {
- const aroundPoint = features[1];
- const aroundFeature = features[0].find(feature => feature[0] === aroundPoint);
- const resultFeature = features[0].find(feature => feature[0] !== aroundPoint);
- const aroundParams = mode === "coordinate" ? `around:${features[2].join(',')}` : `around:${features[2]}`;
- requestBody = `${outJsonTimeout}
- ${getFeatureElement(aroundFeature[0])}(poly:"${query}")[${aroundFeature[1]}];
- ${getFeatureElement(resultFeature[0])}(${aroundParams})[${resultFeature[1]}];
- out geom;`;
- } else {
- if (globalSettings.selectedFeature){
- if (globalSettings.isIncluded){
- requestBody = `${outJsonTimeout}${getFeatureElement(selectedFeatures[0])}[${selectedFeatures[1]}](poly:"${query}");
- ${getFeatureElement(features[0])}(around:${globalSettings.radius})[${features[1]}];
- out geom;`;}
- else {
-
- requestBody = `${outJsonTimeout}(${getFeatureElement(selectedFeatures[0])}[${selectedFeatures[1]}](poly:"${query}");)->.default;
- (${getFeatureElement(features[0])}[${features[1]}](poly:"${query}");)->.all;
- (${getFeatureElement(features[0])}.all(around.default:${globalSettings.radius});)->.inner;
- (.all; - .inner;);
- out geom meta;`
- }
-
- }
- else{
- requestBody = `${outJsonTimeout}${getFeatureElement(features[0])}[${features[1]}](poly:"${query}");out geom;`;}
- }
- return requestBody;
- }
-
- function writeData(coordinates, feature, js,advanced) {
- for (let i = 0; i < coordinates.length; i++) {
- let tag;
- if (coordinates[i].geometry) {
- let nodes = coordinates[i].geometry;
- let medianIndex = Math.floor(nodes.length / 2);
- let medianCoordinate = nodes[medianIndex]
-
- if (coordinates[i].tags && coordinates[i].tags.highway) {
-
- tag = [coordinates[i].tags.highway ,feature];
- } else {
- tag = [feature];
- }
-
- if (medianCoordinate.lat && medianCoordinate.lon) {
- if (advanced=== 'Intersection') {
- tag=['Intersection']
-
- }
- else if(advanced=== 'Around'){
- let advancedTags = [];
- const resultFeature = feature[0].find(feature => feature[0] !== feature[1])
-
- advancedTags.push(resultFeature[0]);
- tag = advancedTags
- if (coordinates[i].tags && coordinates[i].tags.highway) {
- tag .push(coordinates[i].tags.highway);
-
- }
- }
- if(coordinates[i].tags.religion) tag.push(coordinates[i].tags.religion)
- js.customCoordinates.push({
- "lat": medianCoordinate.lat,
- "lng": medianCoordinate.lon,
- "extra": {
- "tags": tag
- }
- });
- }
- }
-
- else if (coordinates[i].lat && coordinates[i].lon && !isCoordinateExists(js.customCoordinates, coordinates[i].lat, coordinates[i].lon)) {
- let tag = [feature];
- if (advanced=== 'Intersection') {
- tag=['Intersection'];
- }
-
- else if(advanced=== 'Around'){
- let advancedTags = [];
- const resultFeature = feature[0].find(feature => feature[0] !== feature[1])
-
- advancedTags.push(resultFeature[0]);
-
- tag = advancedTags
- if (coordinates[i].tags && coordinates[i].tags.highway) {
- tag.push(coordinates[i].tags.highway);
- }
- }
- if(coordinates[i].tags.religion) tag.push(coordinates[i].tags.religion)
- js.customCoordinates.push({
- "lat": coordinates[i].lat,
- "lng": coordinates[i].lon,
- "extra": {
- "tags": tag
- }
- });
- }
-
-
- }
- }
-
- function isCoordinateExists(coordinates, lat, lon) {
- for (let i = 0; i < coordinates.length; i++) {
- if (coordinates[i].lat === lat && coordinates[i].lng === lon) {
- return true;
- }
- }
- return false;
- }
-
- function promptInput(f,a){
- const input = document.createElement('input');
- input.type = 'file';
- input.style.position = 'absolute';
- input.style.right = '450px';
- input.style.top = '15px';
- input.style.display='none'
- input.addEventListener('change', async event => {
- const file = event.target.files[0];
- if (file) {
- try {
- var query = await readFile(file);
- getData(query, 'polygon', f, a);
- document.body.removeChild(input);
- } catch (error) {
- console.error('Error reading file:', error);
- document.body.removeChild(input);
- }
- } else {
- if (document.getElementById('uploadButton')) {
- document.getElementById('uploadButton').remove();
- }
- }
- });
-
- input.click();
-
- document.body.appendChild(input);
-
- input.addEventListener('cancel', () => {
- if (document.getElementById('uploadButton')) {
- document.getElementById('uploadButton').remove();
- }
- });
- }
-
- async function getInput(features, advanced) {
- const { value: upload ,dismiss:inputDismiss} = await Swal.fire({
- title: 'Query Scope Setting',
- text: 'Do you want to upload a GeoJson file? Else you will need to enter a place name or OSM ID to get GeoJson file.You could also draw polygons on the map and download it as GeoJson file from map-making.',
- icon: 'question',
- showCancelButton: true,
- showCloseButton:true,
- allowOutsideClick: false,
- confirmButtonColor: '#3085d6',
- cancelButtonColor: '#d33',
- confirmButtonText: 'Upload',
- cancelButtonText: 'Enter Place Name'
- });
-
- if (upload) {
- promptInput(features,advanced)
-
- }
- else if(inputDismiss==='cancel') {
- await downloadGeoJSONFromOSMID(features,advanced)
- }
- }
-
- function extractCoordinates(p) {
- let results = [];
- if (p.features){
- let polygons=p.features
- polygons.forEach(data => {
- const coordinates = [];
- data.geometry.coordinates.forEach(polygon => {
- polygon[0].forEach(coordinatePair => {
- let coordinate = [coordinatePair[1], coordinatePair[0]].join(' ');
- coordinates.push(coordinate);
- });
- });
- let result = coordinates.join(' ');
- result = result.replace(/,/g, ' ');
- results.push(result);
- });}
- else if( p.coordinates){
- const coordinates = [];
- p.coordinates.forEach(polygon => {
- polygon.forEach(subPolygon => {
- subPolygon.forEach(coordinatePair => {
- let coordinate = [coordinatePair[1], coordinatePair[0]].join(' ');
- coordinates.push(coordinate);
- });
- });
- });
- let result = coordinates.join(' ');
- result = result.replace(/,/g, ' ');
- results.push(result);
-
- }
- else if(p.geometry){
- const coordinates = [];
- p.geometry.coordinates.forEach(polygon => {
- polygon.forEach(subPolygon => {
- subPolygon.forEach(coordinatePair => {
- let coordinate = [coordinatePair[1], coordinatePair[0]].join(' ');
- coordinates.push(coordinate);
- });
- });
- });
- let result = coordinates.join(' ');
- result = result.replace(/,/g, ' ');
- results.push(result);
- }
- else {
- console.error('Invalid Geojson format.');
- alert('Invalid Geojson format!');
- }
- return results;
-
- }
-
- function readFile(file) {
- return new Promise((resolve, reject) => {
- const reader = new FileReader();
-
- reader.onload = function(event) {
- const jsonContent = event.target.result;
-
- try {
- const data = JSON.parse(jsonContent);
-
- if (data) {
- const coordinates = extractCoordinates(data);
- resolve(coordinates);
- }
- } catch (error) {
- console.error('Error parsing Geojson:', error);
- alert('Error parsing Geojson!');
- resolve('error')
- }
- };
-
- reader.readAsText(file);
- });
- }
-
- function runScript(features,advanced){
- if (features&&features.length>0){
- getInput(features,advanced)
- }
- }
-
- function getHtml(categories){
- const categoryKeys = Object.keys(categories);
- const numCategories = categoryKeys.length;
-
-
-
- let html = `<style>${checkboxButtonStyle}</style>`;
-
- for (let i = 0; i < numCategories; i += 2) {
- html += `<div class="category-row">`;
- const category1 = categoryKeys[i];
- const category2 = (i + 1 < numCategories) ? categoryKeys[i + 1] : null;
-
- html += `
- <label class="checkbox-button" for="swal-input-${category1}">
- <input id="swal-input-${category1}" class="swal2-input" type="checkbox" value="${category1}">
- <span>${category1}</span>
- </label>`;
-
- if (category2) {
- html += `
- <label class="checkbox-button" for="swal-input-${category2}">
- <input id="swal-input-${category2}" class="swal2-input" type="checkbox" value="${category2}" >
- <span>${category2}</span>
- </label>
- `;
- } else {
- html += `<div class="flex-fill"></div>`;
- }
-
- html += `</div>`;
- }
- return html
- }
-
- function getFeaturesHtml(features){
- let featuresHtml = '';
- featuresHtml += `<style>${checkboxButtonStyle}</style>`;
-
- for (let i = 0; i < features.length; i += 2) {
- featuresHtml += `<div class="category-row">`;
- const feature1 = features[i];
- const feature2 = (i + 1 < features.length) ? features[i + 1] : null;
-
- featuresHtml += `<div style="display: flex; flex-direction: column; align-items: flex-start;">
- <label class="checkbox-button">
- <input class="swal2-input" type="checkbox" value="${feature1}" style="display: none;">
- <span style="margin-left: 1px;">${feature1}</span>
- </label>
- </div>`;
-
- if (feature2) {
- featuresHtml += `<div style="display: flex; flex-direction: column; align-items: flex-start;">
- <label class="checkbox-button">
- <input class="swal2-input" type="checkbox" value="${feature2}" style="display: none;">
- <span style="margin-left: 1px;">${feature2}</span>
- </label>
- </div>`;
- } else {
-
- featuresHtml += `<div style="flex: 1;"></div>`;
- }
-
- featuresHtml += `</div>`;
- }
- return featuresHtml
- }
-
- async function getFeatures() {
- let selectedCategories = [];
-
- const { value: selectedMainCategories, dismiss: mainCategoriesDismiss } = await Swal.fire({
- title: 'Select Categories',
- html: getHtml(categories),
- focusConfirm: false,
- allowOutsideClick: false,
- showCancelButton: true,
- showCloseButton:true,
- cancelButtonText: 'Advanced Search',
- preConfirm: () => {
-
- selectedCategories = [];
- let noCategorySelected = true;
- for (let category in categories) {
- if (document.getElementById(`swal-input-${category}`).checked) {
- selectedCategories.push(category);
- noCategorySelected = false;
- }
- }
- if (noCategorySelected) {
- Swal.showValidationMessage('Please select at least one category');
- }
-
- return selectedCategories;
- }
- });
-
-
- if (selectedMainCategories) {
- let selectedFeatures = [];
- for (let category of selectedMainCategories) {
- selectedFeatures = selectedFeatures.concat(categories[category]);
- }
- const { value: selectedSubFeatures, dismiss: cancelInput } = await Swal.fire({
- title: 'Select Features',
- html: getFeaturesHtml(selectedFeatures) + '<a href="https://wiki.openstreetmap.org/wiki/Map_features" target="_blank" style="color: black; font-size: 16px;">More information about features...</a>',
- focusConfirm: false,
- allowOutsideClick: 'cancel',
- showCancelButton: true,
- preConfirm: () => {
- let selectedSubFeatures = [];
- const checkboxes = document.querySelectorAll('.swal2-input[type="checkbox"]:checked');
- checkboxes.forEach((checkbox) => {
- selectedSubFeatures.push(checkbox.value);
- });
- if (selectedSubFeatures.length === 0) {
- Swal.showValidationMessage('Please select at least one feature');
- }
-
- return selectedSubFeatures;
- }
- });
-
-
- if (selectedSubFeatures) {
- const features = [];
-
- const filteredTags = taglist.filter(tag => selectedSubFeatures.includes(tag[0]));
-
- features.push(...filteredTags);
- runScript(features,'')
- }
- }
-
- else if (mainCategoriesDismiss === "cancel"){
- const { value: selectedAdvancedCategories, dismiss: cancelInput } = await Swal.fire({
- title: 'Advanced Search',
- html: getHtml(advancedCategories),
- focusConfirm: false,
- allowOutsideClick: false,
- showCancelButton: true,
- showCloseButton:true,
- cancelButtonText: 'Cancel',
- preConfirm: () => {
- selectedCategories = [];
- for (let category in advancedCategories) {
- if (document.getElementById(`swal-input-${category}`).checked) {
- selectedCategories.push(category);
- }
- }
- if (selectedCategories.length === 0) {
- Swal.showValidationMessage('Please select at least one option!');
- return false;
- } else if (selectedCategories.length >1) {
- Swal.showValidationMessage("You're only allowed to select one option!");
- return false;
- }
-
-
- return selectedCategories;
- }
- });
-
- if (selectedAdvancedCategories) {
- let selectedFeatures = [];
- let titleText='Select Features';
- if (selectedAdvancedCategories.includes('Intersection')) {
- titleText = 'Select Major way';
- }
- for (let category of selectedAdvancedCategories) {
- selectedFeatures = selectedFeatures.concat(advancedCategories[category]);
- }
- const { value: selectedSubFeatures, dismiss: cancelInput } = await Swal.fire({
- title: titleText,
- html: getFeaturesHtml(selectedFeatures)+ '<a href="https://wiki.openstreetmap.org/wiki/Map_features" target="_blank" style="color: black; font-size: 16px;">More information about features...</a>',
- focusConfirm: false,
- allowOutsideClick: 'cancel',
- showCancelButton: true,
- preConfirm: () => {
- const checkboxes = document.querySelectorAll('.swal2-input[type="checkbox"]:checked');
- const selectedSubFeatures = Array.from(checkboxes).map(checkbox => checkbox.value);
-
- if (selectedSubFeatures.length < 1) {
- Swal.showValidationMessage('Please select at least one option!');
- return false;
- }
-
- if (selectedAdvancedCategories.includes('Intersection')) {
- const minorFeatures = advancedCategories.Intersection.filter(feature => !selectedSubFeatures.includes(feature));
- return Swal.fire({
- title: 'Select Minor Way',
-
- html: getFeaturesHtml(minorFeatures) + '<a target="_blank" style="color: black; font-size: 14px;">The script will search for intersections based on the type of minor way you selected and the type of major way you selected previously.</a>',
- showCancelButton: true,
- preConfirm: () => {
- return new Promise((resolve) => {
- const checkboxes = document.querySelectorAll('.swal2-input[type="checkbox"]:checked');
- const selectedMinorFeatures = Array.from(checkboxes).map(checkbox => checkbox.value);
- resolve(selectedMinorFeatures);
- }).then((selectedMinorFeatures) => {
- return [selectedSubFeatures, selectedMinorFeatures];
- }).catch(() => {
- return false;
- });
- }
- });
- }
-
- if (selectedAdvancedCategories.includes('Around Search')) {
- return Swal.fire({
- title: 'Select Around Point',
- html: `
- <p>The script will first search for some points that match the feature, and then search around those points for points that match another feature.</p>
- <div>
- <select id="aroundPoint" class="swal2-select">
- ${selectedSubFeatures.map(option => `<option value="${option}">${option}</option>`)}
- </select>
- </div>
- <p>You could also enter a coordinate as around point to search for points that match the features you selected(e.g. 35.12,129.08)</p>
- <div>
- <input type="text" id="coordinate" class="swal2-input">
- </div>
- `,
- showCancelButton: true,
- preConfirm: () => {
- const aroundPoint = document.getElementById('aroundPoint').value;
- const coordinates = document.getElementById('coordinate').value.trim();
-
- const checkFeatures = selectedSubFeatures;
- const aroundPointIndex = checkFeatures.indexOf(aroundPoint);
- checkFeatures.splice(aroundPointIndex, 1);
- const hasRealationFeature = checkFeatures.some(feature => mapFeatures.relation.includes(feature));
- if (hasRealationFeature) {
- Swal.showValidationMessage('Realtion type of points must be set as around point!Better select only one relation type of feature.');
- return false;
- }
- if (isNaN(coordinates) ||selectedSubFeatures.length===1) {
- Swal.showValidationMessage('Please enter a coordinate or select more than 2 features!');
- return false;
-
- if (coordinates) {
- const [latitude, longitude] = coordinates.split(',').map(coord => parseFloat(coord.trim()));
-
- if (isNaN(latitude) || isNaN(longitude)) {
- Swal.showValidationMessage('Please enter a valid coordinate!');
- return false;
- }
- }
-
- return Swal.fire({
- title: 'Please enter a radius(metre)',
- input: 'text',
- inputLabel: 'Radius',
- inputPlaceholder: 'Enter radius...',
- showCancelButton: true,
- inputValue: 100,
- inputValidator: (value) => {
- const radiusValue = parseInt(value);
- if (isNaN(radiusValue) || radiusValue < 10 || radiusValue > 10000) {
- return 'Please enter a valid integer between 10 and 10000!';
- }
- }
- }).then((result) => {
- if (result.isConfirmed) {
- const radius = result.value;
- return [selectedSubFeatures, radius, [latitude, longitude]];
- } else {
- return false;
- }
- });
- } else {
- return Swal.fire({
- title: 'Please enter a radius(metre)',
- input: 'text',
- inputLabel: 'Radius',
- inputPlaceholder: 'Enter radius...',
- showCancelButton: true,
- inputValue: 100,
- inputValidator: (value) => {
- const radiusValue = parseInt(value);
- if (isNaN(radiusValue) || radiusValue < 10 || radiusValue > 10000) {
- return 'Please enter a valid integer between 10 and 10000!';
- }
- }
- }).then((result) => {
- if (result.isConfirmed) {
- const radius = result.value;
- return [selectedSubFeatures, aroundPoint, radius];
- } else {
- return false;
- }
- });
- }
- }
- });
- }
-
- else{return selectedSubFeatures}}
-
- });
- if (selectedSubFeatures) {
-
- const features = [];
- let filteredTags;
- if (selectedAdvancedCategories.includes('Intersection')){
- const intersectionFeatures=selectedSubFeatures.value
- let majorTags
- let minorTags
-
- features.push(...[intersectionFeatures[0]],...[intersectionFeatures[1]]);
-
- runScript(features,'Intersection')
- }
- if (selectedAdvancedCategories.includes('Around Search')){
- let aroundFeatures=selectedSubFeatures.value
- filteredTags = taglist.filter(tag => aroundFeatures[0].includes(tag[0]))
- features.push(...filteredTags)
- if (Array.isArray(aroundFeatures[2])) {
- getData('','coordinate',[features,aroundFeatures[1],aroundFeatures[2]],'Around')}
- else{
- runScript([features,aroundFeatures[1],aroundFeatures[2]],'Around')}
- }
- }
- }
- }
- }
-
- async function downloadGeoJSONFromOSMID(f,a) {
- Swal.fire({
- title: 'Enter OSM ID or place name',
- input: 'text',
- inputValue: 'Paris or 71525',
- showCancelButton: true,
- confirmButtonText: 'Submit',
- cancelButtonText: 'Cancel',
- showCloseButton:true,
- inputValidator: (value) => {
- if (!value) {
- return 'You need to enter something!';
- }
- }
- }).then(async (result) => {
- if (result.isConfirmed) {
- const userInput = result.value;
- if (!isNaN(userInput)) {
- await downloadGeoJSON(userInput);
- } else {
- try {
- const osmID = await getOSMID(userInput);
- if (osmID) {
- await downloadGeoJSON(osmID);
- if(f||a){
- setTimeout(function() {promptInput(f,a)},500)
- }
- } else {
- Swal.fire('Error', 'OSM ID not found for the provided place name.', 'error');
- }
- } catch (error) {
- console.error('Error:', error);
- }
- }
- } else if (result.dismiss === Swal.DismissReason.cancel) {
- console.log('No input provided.');
- }
- });}
-
- async function getOSMID(placeName) {
- const nominatimURL = `https://nominatim.openstreetmap.org/search?format=json&q=${placeName}&addressdetails=1`;
- const response = await fetch(nominatimURL, {
- headers: {
- 'Accept-Language': 'en-US,en;q=0.9'
- }
- });
- const data = await response.json();
- if (data.length > 0) {
- let options = {};
- for (let i = 0; i < Math.min(5, data.length); i++) {
- options[i + 1] = `${data[i].display_name}\n${data[i].address.country}`;
- }
-
- const { value: chosenIndex } = await Swal.fire({
- title: "Choose a location",
- input: 'select',
- inputOptions: options,
- showCancelButton:true,
- inputValidator: (value) => {
- if (value === '') {
- return 'You must select a location';
- }
- }
- });
-
- if (chosenIndex !== '') {
- const index = parseInt(chosenIndex);
- return data[index - 1].osm_id;
- } else {
- return null;
- }
- } else {
- return null;
- }
- }
-
- async function downloadGeoJSON(osmID) {
- const url = `https://polygons.openstreetmap.fr/get_geojson.py?id=${osmID}`;
-
- try {
- const response = await fetch(url);
- if (!response.ok) {
- throw new Error('Failed to fetch GeoJSON data.');
- }
-
- const data = await response.json();
- const geojsonString = JSON.stringify(data);
- const blob = new Blob([geojsonString], { type: 'application/json' });
- const link = document.createElement('a');
- link.href = window.URL.createObjectURL(blob);
- link.download = `osm_boundary_${osmID}.geojson`;
- link.click();
- } catch (error) {
- console.error('Error downloading GeoJSON:', error);
- alert('Error downloading GeoJSON')
- }
- }
-
-
- var triggerButton = document.createElement("button");
- triggerButton.innerHTML = "Feature Your Map";
- triggerButton.style.position = 'absolute';
- triggerButton.style.right = '10px';
- triggerButton.style.top = '8px';
- triggerButton.style.width='160px'
- triggerButton.style.fontSize='16px'
- triggerButton.style.borderRadius = "16px";
- triggerButton.style.padding = "5px 5px";
- triggerButton.style.border = "none";
- triggerButton.style.backgroundColor = "#4CAF50";
- triggerButton.style.color = "white";
- triggerButton.style.cursor = "pointer";
- document.body.appendChild(triggerButton);
-
- var button = document.createElement('button');
- button.textContent = 'Download GeoJSON'
- button.style.position = 'absolute';
- button.style.right = '180px';
- button.style.top = '8px';
- button.style.width='160px'
- button.style.fontSize='16px'
- button.style.borderRadius = "16px";
- button.style.padding = "5px 5px";
- button.style.border = "none";
- button.style.backgroundColor = "#4CAF50";
- button.style.color = "white";
- button.style.cursor = "pointer";
- document.body.appendChild(button);
-
- var settingButton = document.createElement('button');
- settingButton.textContent = 'Default Setting'
- settingButton.style.position = 'absolute';
- settingButton.style.right = '350px';
- settingButton.style.top = '8px';
- settingButton.style.width='160px'
- settingButton.style.fontSize='16px'
- settingButton.style.borderRadius = "16px";
- settingButton.style.padding = "5px 5px";
- settingButton.style.border = "none";
- settingButton.style.backgroundColor = "#4CAF50";
- settingButton.style.color = "white";
- settingButton.style.cursor = "pointer";
- document.body.appendChild(settingButton);
-
- settingButton.addEventListener('click', getSettings)
- button.addEventListener('click', downloadGeoJSONFromOSMID);
- triggerButton.addEventListener("click", getFeatures);
- })();