Greasy Fork is available in English.

Feature Your Map

get poi data from osm

// ==UserScript==
// @name         Feature Your Map
// @version      2.4
// @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']}

    let taglist = [['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'],
        '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);

                        }
                    }
                    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);
                    }
                }
                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 => {
                let 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){
            let 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 {
            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&bounded=1&viewbox=-180,-90,180,90`;
        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 = 'fixed';
    triggerButton.style.right = '20px';
    triggerButton.style.top = '12px';
    triggerButton.style.borderRadius = "16px";
    triggerButton.style.padding = "10px 20px";
    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 = 'fixed';
    button.style.right = '180px';
    button.style.top = '12px';
    button.style.borderRadius = "16px";
    button.style.padding = "10px 20px";
    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 = 'fixed';
    settingButton.style.right = '360px';
    settingButton.style.top = '12px';
    settingButton.style.borderRadius = "16px";
    settingButton.style.padding = "10px 20px";
    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);

})();