Feature Your Map

get poi data from osm

  1. // ==UserScript==
  2. // @name Feature Your Map
  3. // @version 2.4.2
  4. // @description get poi data from osm
  5. // @author KaKa
  6. // @match https://map-making.app/maps/*
  7. // @grant GM_setClipboard
  8. // @license MIT
  9. // @icon https://www.google.com/s2/favicons?domain=geoguessr.com
  10. // @require https://cdn.jsdelivr.net/npm/sweetalert2@11
  11. // @namespace http://tampermonkey.net/
  12. // ==/UserScript==
  13.  
  14. (function() {
  15. 'use strict';
  16. document.addEventListener('DOMContentLoaded', function() {
  17. document.addEventListener('change', function(event) {
  18. const checkbox = event.target;
  19. if (checkbox.type === 'checkbox') {
  20. const label = checkbox.parentElement;
  21. if (checkbox.checked) {
  22. label.classList.add('checked');
  23. } else {
  24. label.classList.remove('checked');
  25. }
  26. }
  27. });
  28. });
  29.  
  30. let globalSettings = {
  31. selectedFeature: null,
  32. isIncluded: null,
  33. radius: null
  34. };
  35.  
  36. let mapFeatures={'way':['motorway','trunk','primary','secondary','tertiary','unclassified','footway','path','pedestrain','river','bridge','tunnel','roundabout','coastline'],
  37. 'node':[ 'bus stop', 'level crossing','milestone', 'crosswalk','traffic light','postbox', 'hydrant','utility pole', 'lamppost','waste basket',
  38. 'waste disposal','yield sign','stop sign','stadium','museum','school','motorway junction','tree','volcano','cape','hill'],
  39. 'relation':['grassland','forest','residental','farmland','meadow','paddy','vineyard'],
  40. 'nw':['bollard','hospital','train station','religious','government','hotel','estate agent']}
  41.  
  42. let taglist = [['estate agent','"shop"="estate_agent"'],
  43. ['coastline','"natural"="coastline"'],
  44. ['bollard','"barrier"="bollard"'],
  45. ['vineyard','"landuse"="vineyard"'],
  46. ['paddy','"landuse"="paddy"'],
  47. ['meadow','"landuse"="meadow"'],
  48. ['residental','"landuse"="residental"'],
  49. ['farmland','"landuse"="farmland"'],
  50. ['hill','"natural"="hill"'],
  51. ['volcano', '"natural"="volcano"'],
  52. ['grassland','"natural"="grassland"'],
  53. ['forest','"natural"="wood"'],
  54. ['cape', '"natural"="cape"'] ,
  55. ['tree', '"natural"="tree"'],
  56. ['bridge', '"bridge"="yes"'],
  57. ['bus stop', '"highway"="bus_stop"'],
  58. ['utility pole', '"power"="pole"'],
  59. ['traffic light', '"highway"="traffic_signals"'],
  60. ['lamppost', '"highway"="street_lamp"'],
  61. ['crosswalk', '"highway"="crossing"'],
  62. ['level crossing', '"railway"="level_crossing"'],
  63. ['postbox', '"amenity"="post_box"'],
  64. ['hydrant', '"emergency"="fire_hydrant"'],
  65. ['milestone', '"highway"="milestone"'],
  66. ['motorway','"highway"="motorway"'],
  67. ['trunk','"highway"="trunk"'],
  68. ['primary','"highway"="primary"'],
  69. ['secondary','"highway"="secondary"'],
  70. ['tertiary','"highway"="tertiary"'],
  71. ['unclassified','"highway"="unclassified"'],
  72. ['footway','"highway"="footway"'],
  73. ['path','"highway"="path"'],
  74. ['pedestrain','"highway"="pedestrain"'],
  75. ['river','"waterway"="river"'],
  76. ['railway','"railway"="rail"'],
  77. ['tram','"railway"="tram"'],
  78. ['tunnel','"tunnel"="yes"'],
  79. ['yield sign','"highway"="give_way"'],
  80. ['roundabout','"junction"="roundabout"'],
  81. ['waste basket','"amenity"="waste_basket"'],
  82. ['waste disposal','"amenity"="waste_disposal"'],
  83. ['hospital','"amenity"="hospital"'],
  84. ['government','"building"="government"'],
  85. ['religious','"amenity"="place_of_worship"'],
  86. ['stop sign','"highway"="stop"'],
  87. ['museum','"building"="museum"'],
  88. ['train station','"building"="train_station"'],
  89. ['stadium','"leisure"="stadium"'],
  90. ['school','"amenity"="school"'],
  91. ['hotel','"building"="hotel"'],
  92. ['motorway junction','"highway"="motorway_junction"']
  93. ];
  94.  
  95. let categories = {
  96. 'Traffic': ['bridge', 'roundabout','tunnel', 'level crossing','bollard','milestone', 'crosswalk','yield sign','stop sign','motorway junction'],
  97. 'Public Facility': ['bus stop','postbox', 'hydrant','utility pole', 'lamppost','traffic light','waste basket','waste disposal'],
  98. 'Building':['government','school','hospital','stadium','museum','religious','hotel','estate agent'],
  99. 'Natural':['volcano','tree','cape','hill']};
  100.  
  101. let advancedCategories={'Intersection':['motorway','trunk','primary','secondary','tertiary','unclassified','footway','path','pedestrain'],
  102. 'Around Search':['bridge', 'bus stop', 'level crossing','milestone', 'crosswalk','traffic light','postbox', 'hydrant','utility pole',
  103. 'lamppost','river','government','school','hospital','waste basket','waste disposal','stadium','museum','religious',
  104. 'tunnel','roundabout','hotel','motorway junction','tree','volcano','cape','coastline','hill','forest','grassland',
  105. 'residental','farmland','meadow','paddy','vineyard']}
  106.  
  107. const API_URL = "https://overpass-api.de/api/interpreter";
  108. const checkboxButtonStyle = `.checkbox-button {
  109. display: inline-block;
  110. cursor: pointer;
  111. background-color: #007bff;
  112. color: #fff;
  113. padding: 5px 10px;
  114. border-radius: 5px;
  115. margin-right: 10px;}
  116.  
  117. .checkbox-button:hover {
  118. background-color: #4CAF50;
  119. border-color: #4CAF50;}
  120.  
  121. .checkbox-button input[type="checkbox"] {
  122. display: none;}
  123.  
  124. .checkbox-button.checked {
  125. background-color: #4CAF50;
  126. color: #fff;
  127. font-weight: bold;
  128. border-color: #4CAF50;}
  129.  
  130. .category-item {
  131. display: flex;
  132. flex-direction: column;
  133. align-items: flex-start;}
  134.  
  135. .category-row {
  136. display: flex;
  137. justify-content: space-between;
  138. margin-bottom: 20px;
  139. margin-right: 30px;
  140. margin-left: 30px;}
  141.  
  142. .flex-fill {
  143. flex: 1;}`;
  144.  
  145. async function fetchData(query, mode,feature,advanced) {
  146. const requestBody = getRequestBody(feature,mode,advanced,query)
  147. const response = await fetch(API_URL, {
  148. method: "POST",
  149. body: "data=" + encodeURIComponent(requestBody),
  150. });
  151.  
  152. if (!response.ok) {
  153. throw new Error("Network response was not ok");
  154. }
  155.  
  156. return response.json();
  157. }
  158.  
  159. async function getData(query, mode,features,advanced) {
  160. try {
  161. const swal = Swal.fire({
  162. title: 'Fetching Coordinates',
  163. text: 'Please wait...',
  164. allowOutsideClick: false,
  165. allowEscapeKey: false,
  166. showConfirmButton: false,
  167. didOpen: () => {
  168. Swal.showLoading();
  169. }
  170. });
  171. const js = {
  172. "name": "",
  173. "customCoordinates": [],
  174. "extra": {
  175. "tags": {},
  176. "infoCoordinates": []
  177. }
  178. };
  179. let elements = [];
  180. if (advanced==='Intersection'){
  181. const response=await fetchData(query, mode, features,advanced)
  182. if (response.remark && response.remark.includes("runtime error")) {
  183. alert("RAM runned out or query timed out. Please try narrowing your search scope.");
  184.  
  185. } else if (response.elements && response.elements.length > 0) {
  186. elements.push(...response.elements);
  187. }
  188. writeData(elements, features, js,advanced);
  189. }
  190.  
  191. else if(advanced==='Around'){
  192. const response=await fetchData(query, mode, features,advanced)
  193. if (response.remark && response.remark.includes("runtime error")) {
  194. alert("RAM runned out or query timed out. Please try narrowing your search scope.");
  195.  
  196. } else if (response.elements && response.elements.length > 0) {
  197. elements.push(...response.elements);
  198. }
  199. writeData(elements, features, js,advanced)}
  200. else{
  201. for (let feature of features) {
  202. let requests = [];
  203.  
  204. requests.push(fetchData(query, mode, feature));
  205.  
  206. const responses = await Promise.all(requests);
  207.  
  208. responses.forEach(response => {if (response.remark && response.remark.includes("runtime error")) {
  209. alert("RAM runned out or query timed out. Please try narrowing your search scope.");
  210. } else
  211. if (response.elements && response.elements.length > 0) {
  212. elements.push(...response.elements);
  213. }
  214. });
  215. writeData(elements, feature[0], js);
  216.  
  217. }
  218. }
  219. if (js.customCoordinates.length === 0) {
  220. swal.close()
  221. if (mode === 'area') {
  222. Swal.fire('Error',"None retrived!The place name you entered may be incorrect,couldn't find this place.",'error');
  223. } else if (mode === 'polygon') {
  224. Swal.fire('Error',"None retrived!Please check if your geojson file format is correct.",'error');
  225. }
  226. else{
  227. Swal.fire('Error',"None retrived!Please narrow the radius or select a less features combination.",'error')}
  228. }
  229. if (js.customCoordinates.length > 0) {
  230. swal.close()
  231. GM_setClipboard(JSON.stringify(js));
  232. Swal.fire('Success',"JSON data has been copied to your clipboard!",'success');
  233. }
  234. } catch (error) {
  235. Swal.fire('Error',`Error fetching data${error}:`,'error');
  236. }
  237. }
  238.  
  239. function getFeatureElement(f){
  240. for (const key in mapFeatures) {
  241. if (mapFeatures.hasOwnProperty(key)) {
  242. if (mapFeatures[key].includes(f)) {
  243. return key
  244. }}}}
  245.  
  246. function getCategoryHtml(categories) {
  247. let html = '';
  248. for (let category in categories) {
  249. html += `<input type="radio" name="category" value="${category}" id="swal-input-${category}">
  250. <label for="swal-input-${category}">${category}</label><br>`;
  251. }
  252. return html;
  253. }
  254.  
  255. function getSettingFeaturesHtml(features) {
  256. let html = '';
  257. for (let feature of features) {
  258. html += `<input type="radio" name="feature" value="${feature}" id="swal-input-${feature}">
  259. <label for="swal-input-${feature}">${feature}</label><br>`;
  260. }
  261. return html;
  262. }
  263.  
  264. async function getSettings() {
  265. const resetSettings = () => {
  266. globalSettings.selectedFeature = null;
  267. globalSettings.isIncluded = null;
  268. globalSettings.radius = null;
  269. };
  270.  
  271. const setSettings = (feature, isIncluded, radius) => {
  272. globalSettings.selectedFeature = feature;
  273. globalSettings.isIncluded = isIncluded;
  274. globalSettings.radius = radius;
  275. };
  276.  
  277. resetSettings();
  278. let settingCategories = {
  279. 'Transportation': ['bridge', 'roundabout', 'tunnel', 'level crossing', 'milestone', 'crosswalk', 'yield sign', 'stop sign', 'motorway junction'],
  280. 'Public Facility': ['bus stop', 'postbox', 'hydrant', 'utility pole', 'lamppost', 'traffic light', 'waste basket', 'waste disposal'],
  281. 'Building': ['government', 'school', 'hospital', 'stadium', 'museum', 'religious', 'hotel'],
  282. 'Natural': ['volcano', 'tree', 'cape', 'hill', 'forest', 'grassland', 'coastline'],
  283. 'Landuse': ['farmland', 'paddy', 'meadow', 'vineyard', 'residental']
  284. };
  285.  
  286. let selectedSettingCategory = null;
  287.  
  288. const { value: selectedCategory, dismiss: initializeSettings } = await Swal.fire({
  289. title: 'Setting Categories',
  290. html: getCategoryHtml(settingCategories),
  291. focusConfirm: false,
  292. allowOutsideClick: false,
  293. showCancelButton: true,
  294. showCloseButton: true,
  295. cancelButtonText: 'Reset Settings',
  296. preConfirm: () => {
  297. const selectedCategoryRadio = document.querySelector('input[name="category"]:checked');
  298. if (!selectedCategoryRadio) {
  299. Swal.showValidationMessage('Please select a category');
  300. return;
  301. }
  302. selectedSettingCategory = selectedCategoryRadio.value;
  303. return selectedSettingCategory;
  304. }
  305. });
  306.  
  307. if (initializeSettings=='cancel') {
  308. Swal.fire({
  309. icon: 'success',
  310. title: 'Settings Reset',
  311. text: 'Your settings have been successfully reset.',
  312. showConfirmButton: false,
  313. timer: 1500
  314. });
  315. resetSettings();
  316.  
  317. }
  318.  
  319. if (selectedCategory) {
  320. let selectedSettingFeatures = settingCategories[selectedSettingCategory];
  321.  
  322. const { value: selectedFeature, dismiss: cancelInput } = await Swal.fire({
  323. title: 'Setting Features',
  324. 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>',
  325. focusConfirm: false,
  326. allowOutsideClick: false,
  327. showCancelButton: true,
  328. preConfirm: () => {
  329. const selectedFeatureRadio = document.querySelector('input[name="feature"]:checked');
  330. if (!selectedFeatureRadio) {
  331. Swal.showValidationMessage('Please select a feature');
  332. return;
  333. }
  334. return selectedFeatureRadio.value;
  335. }
  336. });
  337.  
  338. if (selectedFeature) {
  339. const { value: isIncluded, dismiss: cancelInclude } = await Swal.fire({
  340. title: 'Select POI Range',
  341. text: 'Do you want to include POIs within a certain range or outside of it?',
  342. icon: 'question',
  343. showCancelButton: true,
  344. confirmButtonText: 'Within Range',
  345. cancelButtonText: 'Outside Range',
  346. allowOutsideClick:false,
  347. });
  348.  
  349. let radius = 50;
  350. if (isIncluded !== Swal.DismissReason.cancel) {
  351. const { value: inputRadius, dismiss: cancelRadius } = await Swal.fire({
  352. title: 'Enter Radius',
  353. input: 'number',
  354. inputLabel: 'Radius (in meters)',
  355. inputPlaceholder: 'Enter the radius for POIs',
  356. inputAttributes: {
  357. min: 5,
  358. max: 10000,
  359. step: 100,
  360. },
  361. showCancelButton: true,
  362. confirmButtonText: 'OK',
  363. cancelButtonText: 'Cancel',
  364. allowOutsideClick:false,
  365. inputValidator: (value) => {
  366. if (!value) {
  367. return 'You need to enter a radius!';
  368. }
  369. const radiusValue = parseInt(value);
  370. if (radiusValue < 5 || radiusValue > 10000) {
  371. return 'Radius must be between 5 and 10000 meters!';
  372. }
  373. }
  374. });
  375.  
  376. if (inputRadius !== undefined && inputRadius !== null) {
  377. radius = parseInt(inputRadius);
  378. }
  379. }
  380.  
  381. if (selectedFeature && isIncluded !== Swal.DismissReason.cancel) {
  382. const filteredTags = taglist.filter(tag => selectedFeature.includes(tag[0]));
  383. setSettings(filteredTags[0], isIncluded, radius);
  384. Swal.fire({
  385. icon: 'success',
  386. title: 'Settings Updated',
  387. text: 'Your settings have been successfully updated.',
  388. showConfirmButton: false,
  389. timer: 1200
  390. });
  391. }
  392. }
  393. }
  394. }
  395.  
  396. function getRequestBody(features, mode, advanced, query) {
  397. let requestBody = "";
  398. var selectedFeatures=globalSettings.selectedFeature
  399. const outJsonTimeout = "[out:json][timeout:180];";
  400. if (advanced === "Intersection") {
  401. if (globalSettings.selectedFeature){
  402. if (globalSettings.isIncluded){
  403. requestBody = `${outJsonTimeout}${getFeatureElement(selectedFeatures[0])}[${selectedFeatures[1]}](poly:"${query}");
  404. way(poly:"${query}")[highway~"^(${features[0].join('|')})$"]->.w1;
  405. way(poly:"${query}")[highway~"^(${features[1].join('|')})$"]->.w2;
  406. node(w.w1)(w.w2)(around:${globalSettings.radius});
  407. out geom;`;}
  408. else {
  409. requestBody = `${outJsonTimeout}(${getFeatureElement(selectedFeatures[0])}[${selectedFeatures[1]}](poly:"${query}");)->.default;
  410. way(poly:"${query}")[highway~"^(${features[0].join('|')})$"]->.w1;
  411. way(poly:"${query}")[highway~"^(${features[1].join('|')})$"]->.w2;
  412. node(w.w1)(w.w2)->.all;
  413. (node.all(around.default:${globalSettings.radius});)->.inner;
  414. (.all; - .inner;);
  415. out geom meta;`
  416. }
  417. }
  418. else {
  419. requestBody = `${outJsonTimeout}
  420. way(poly:"${query}")[highway~"^(${features[0].join('|')})$"]->.w1;
  421. way(poly:"${query}")[highway~"^(${features[1].join('|')})$"]->.w2;
  422. node(w.w1)(w.w2);
  423. out geom;`;}
  424. } else if (advanced === "Around") {
  425. const aroundPoint = features[1];
  426. const aroundFeature = features[0].find(feature => feature[0] === aroundPoint);
  427. const resultFeature = features[0].find(feature => feature[0] !== aroundPoint);
  428. const aroundParams = mode === "coordinate" ? `around:${features[2].join(',')}` : `around:${features[2]}`;
  429. requestBody = `${outJsonTimeout}
  430. ${getFeatureElement(aroundFeature[0])}(poly:"${query}")[${aroundFeature[1]}];
  431. ${getFeatureElement(resultFeature[0])}(${aroundParams})[${resultFeature[1]}];
  432. out geom;`;
  433. } else {
  434. if (globalSettings.selectedFeature){
  435. if (globalSettings.isIncluded){
  436. requestBody = `${outJsonTimeout}${getFeatureElement(selectedFeatures[0])}[${selectedFeatures[1]}](poly:"${query}");
  437. ${getFeatureElement(features[0])}(around:${globalSettings.radius})[${features[1]}];
  438. out geom;`;}
  439. else {
  440.  
  441. requestBody = `${outJsonTimeout}(${getFeatureElement(selectedFeatures[0])}[${selectedFeatures[1]}](poly:"${query}");)->.default;
  442. (${getFeatureElement(features[0])}[${features[1]}](poly:"${query}");)->.all;
  443. (${getFeatureElement(features[0])}.all(around.default:${globalSettings.radius});)->.inner;
  444. (.all; - .inner;);
  445. out geom meta;`
  446. }
  447.  
  448. }
  449. else{
  450. requestBody = `${outJsonTimeout}${getFeatureElement(features[0])}[${features[1]}](poly:"${query}");out geom;`;}
  451. }
  452. return requestBody;
  453. }
  454.  
  455. function writeData(coordinates, feature, js,advanced) {
  456. for (let i = 0; i < coordinates.length; i++) {
  457. let tag;
  458. if (coordinates[i].geometry) {
  459. let nodes = coordinates[i].geometry;
  460. let medianIndex = Math.floor(nodes.length / 2);
  461. let medianCoordinate = nodes[medianIndex]
  462.  
  463. if (coordinates[i].tags && coordinates[i].tags.highway) {
  464.  
  465. tag = [coordinates[i].tags.highway ,feature];
  466. } else {
  467. tag = [feature];
  468. }
  469.  
  470. if (medianCoordinate.lat && medianCoordinate.lon) {
  471. if (advanced=== 'Intersection') {
  472. tag=['Intersection']
  473.  
  474. }
  475. else if(advanced=== 'Around'){
  476. let advancedTags = [];
  477. const resultFeature = feature[0].find(feature => feature[0] !== feature[1])
  478.  
  479. advancedTags.push(resultFeature[0]);
  480. tag = advancedTags
  481. if (coordinates[i].tags && coordinates[i].tags.highway) {
  482. tag .push(coordinates[i].tags.highway);
  483.  
  484. }
  485. }
  486. if(coordinates[i].tags.religion) tag.push(coordinates[i].tags.religion)
  487. js.customCoordinates.push({
  488. "lat": medianCoordinate.lat,
  489. "lng": medianCoordinate.lon,
  490. "extra": {
  491. "tags": tag
  492. }
  493. });
  494. }
  495. }
  496.  
  497. else if (coordinates[i].lat && coordinates[i].lon && !isCoordinateExists(js.customCoordinates, coordinates[i].lat, coordinates[i].lon)) {
  498. let tag = [feature];
  499. if (advanced=== 'Intersection') {
  500. tag=['Intersection'];
  501. }
  502.  
  503. else if(advanced=== 'Around'){
  504. let advancedTags = [];
  505. const resultFeature = feature[0].find(feature => feature[0] !== feature[1])
  506.  
  507. advancedTags.push(resultFeature[0]);
  508.  
  509. tag = advancedTags
  510. if (coordinates[i].tags && coordinates[i].tags.highway) {
  511. tag.push(coordinates[i].tags.highway);
  512. }
  513. }
  514. if(coordinates[i].tags.religion) tag.push(coordinates[i].tags.religion)
  515. js.customCoordinates.push({
  516. "lat": coordinates[i].lat,
  517. "lng": coordinates[i].lon,
  518. "extra": {
  519. "tags": tag
  520. }
  521. });
  522. }
  523.  
  524.  
  525. }
  526. }
  527.  
  528. function isCoordinateExists(coordinates, lat, lon) {
  529. for (let i = 0; i < coordinates.length; i++) {
  530. if (coordinates[i].lat === lat && coordinates[i].lng === lon) {
  531. return true;
  532. }
  533. }
  534. return false;
  535. }
  536.  
  537. function promptInput(f,a){
  538. const input = document.createElement('input');
  539. input.type = 'file';
  540. input.style.position = 'absolute';
  541. input.style.right = '450px';
  542. input.style.top = '15px';
  543. input.style.display='none'
  544. input.addEventListener('change', async event => {
  545. const file = event.target.files[0];
  546. if (file) {
  547. try {
  548. var query = await readFile(file);
  549. getData(query, 'polygon', f, a);
  550. document.body.removeChild(input);
  551. } catch (error) {
  552. console.error('Error reading file:', error);
  553. document.body.removeChild(input);
  554. }
  555. } else {
  556. if (document.getElementById('uploadButton')) {
  557. document.getElementById('uploadButton').remove();
  558. }
  559. }
  560. });
  561.  
  562. input.click();
  563.  
  564. document.body.appendChild(input);
  565.  
  566. input.addEventListener('cancel', () => {
  567. if (document.getElementById('uploadButton')) {
  568. document.getElementById('uploadButton').remove();
  569. }
  570. });
  571. }
  572.  
  573. async function getInput(features, advanced) {
  574. const { value: upload ,dismiss:inputDismiss} = await Swal.fire({
  575. title: 'Query Scope Setting',
  576. 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.',
  577. icon: 'question',
  578. showCancelButton: true,
  579. showCloseButton:true,
  580. allowOutsideClick: false,
  581. confirmButtonColor: '#3085d6',
  582. cancelButtonColor: '#d33',
  583. confirmButtonText: 'Upload',
  584. cancelButtonText: 'Enter Place Name'
  585. });
  586.  
  587. if (upload) {
  588. promptInput(features,advanced)
  589.  
  590. }
  591. else if(inputDismiss==='cancel') {
  592. await downloadGeoJSONFromOSMID(features,advanced)
  593. }
  594. }
  595.  
  596. function extractCoordinates(p) {
  597. let results = [];
  598. if (p.features){
  599. let polygons=p.features
  600. polygons.forEach(data => {
  601. const coordinates = [];
  602. data.geometry.coordinates.forEach(polygon => {
  603. polygon[0].forEach(coordinatePair => {
  604. let coordinate = [coordinatePair[1], coordinatePair[0]].join(' ');
  605. coordinates.push(coordinate);
  606. });
  607. });
  608. let result = coordinates.join(' ');
  609. result = result.replace(/,/g, ' ');
  610. results.push(result);
  611. });}
  612. else if( p.coordinates){
  613. const coordinates = [];
  614. p.coordinates.forEach(polygon => {
  615. polygon.forEach(subPolygon => {
  616. subPolygon.forEach(coordinatePair => {
  617. let coordinate = [coordinatePair[1], coordinatePair[0]].join(' ');
  618. coordinates.push(coordinate);
  619. });
  620. });
  621. });
  622. let result = coordinates.join(' ');
  623. result = result.replace(/,/g, ' ');
  624. results.push(result);
  625.  
  626. }
  627. else if(p.geometry){
  628. const coordinates = [];
  629. p.geometry.coordinates.forEach(polygon => {
  630. polygon.forEach(subPolygon => {
  631. subPolygon.forEach(coordinatePair => {
  632. let coordinate = [coordinatePair[1], coordinatePair[0]].join(' ');
  633. coordinates.push(coordinate);
  634. });
  635. });
  636. });
  637. let result = coordinates.join(' ');
  638. result = result.replace(/,/g, ' ');
  639. results.push(result);
  640. }
  641. else {
  642. console.error('Invalid Geojson format.');
  643. alert('Invalid Geojson format!');
  644. }
  645. return results;
  646.  
  647. }
  648.  
  649. function readFile(file) {
  650. return new Promise((resolve, reject) => {
  651. const reader = new FileReader();
  652.  
  653. reader.onload = function(event) {
  654. const jsonContent = event.target.result;
  655.  
  656. try {
  657. const data = JSON.parse(jsonContent);
  658.  
  659. if (data) {
  660. const coordinates = extractCoordinates(data);
  661. resolve(coordinates);
  662. }
  663. } catch (error) {
  664. console.error('Error parsing Geojson:', error);
  665. alert('Error parsing Geojson!');
  666. resolve('error')
  667. }
  668. };
  669.  
  670. reader.readAsText(file);
  671. });
  672. }
  673.  
  674. function runScript(features,advanced){
  675. if (features&&features.length>0){
  676. getInput(features,advanced)
  677. }
  678. }
  679.  
  680. function getHtml(categories){
  681. const categoryKeys = Object.keys(categories);
  682. const numCategories = categoryKeys.length;
  683.  
  684.  
  685.  
  686. let html = `<style>${checkboxButtonStyle}</style>`;
  687.  
  688. for (let i = 0; i < numCategories; i += 2) {
  689. html += `<div class="category-row">`;
  690. const category1 = categoryKeys[i];
  691. const category2 = (i + 1 < numCategories) ? categoryKeys[i + 1] : null;
  692.  
  693. html += `
  694. <label class="checkbox-button" for="swal-input-${category1}">
  695. <input id="swal-input-${category1}" class="swal2-input" type="checkbox" value="${category1}">
  696. <span>${category1}</span>
  697. </label>`;
  698.  
  699. if (category2) {
  700. html += `
  701. <label class="checkbox-button" for="swal-input-${category2}">
  702. <input id="swal-input-${category2}" class="swal2-input" type="checkbox" value="${category2}" >
  703. <span>${category2}</span>
  704. </label>
  705. `;
  706. } else {
  707. html += `<div class="flex-fill"></div>`;
  708. }
  709.  
  710. html += `</div>`;
  711. }
  712. return html
  713. }
  714.  
  715. function getFeaturesHtml(features){
  716. let featuresHtml = '';
  717. featuresHtml += `<style>${checkboxButtonStyle}</style>`;
  718.  
  719. for (let i = 0; i < features.length; i += 2) {
  720. featuresHtml += `<div class="category-row">`;
  721. const feature1 = features[i];
  722. const feature2 = (i + 1 < features.length) ? features[i + 1] : null;
  723.  
  724. featuresHtml += `<div style="display: flex; flex-direction: column; align-items: flex-start;">
  725. <label class="checkbox-button">
  726. <input class="swal2-input" type="checkbox" value="${feature1}" style="display: none;">
  727. <span style="margin-left: 1px;">${feature1}</span>
  728. </label>
  729. </div>`;
  730.  
  731. if (feature2) {
  732. featuresHtml += `<div style="display: flex; flex-direction: column; align-items: flex-start;">
  733. <label class="checkbox-button">
  734. <input class="swal2-input" type="checkbox" value="${feature2}" style="display: none;">
  735. <span style="margin-left: 1px;">${feature2}</span>
  736. </label>
  737. </div>`;
  738. } else {
  739.  
  740. featuresHtml += `<div style="flex: 1;"></div>`;
  741. }
  742.  
  743. featuresHtml += `</div>`;
  744. }
  745. return featuresHtml
  746. }
  747.  
  748. async function getFeatures() {
  749. let selectedCategories = [];
  750.  
  751. const { value: selectedMainCategories, dismiss: mainCategoriesDismiss } = await Swal.fire({
  752. title: 'Select Categories',
  753. html: getHtml(categories),
  754. focusConfirm: false,
  755. allowOutsideClick: false,
  756. showCancelButton: true,
  757. showCloseButton:true,
  758. cancelButtonText: 'Advanced Search',
  759. preConfirm: () => {
  760.  
  761. selectedCategories = [];
  762. let noCategorySelected = true;
  763. for (let category in categories) {
  764. if (document.getElementById(`swal-input-${category}`).checked) {
  765. selectedCategories.push(category);
  766. noCategorySelected = false;
  767. }
  768. }
  769. if (noCategorySelected) {
  770. Swal.showValidationMessage('Please select at least one category');
  771. }
  772.  
  773. return selectedCategories;
  774. }
  775. });
  776.  
  777.  
  778. if (selectedMainCategories) {
  779. let selectedFeatures = [];
  780. for (let category of selectedMainCategories) {
  781. selectedFeatures = selectedFeatures.concat(categories[category]);
  782. }
  783. const { value: selectedSubFeatures, dismiss: cancelInput } = await Swal.fire({
  784. title: 'Select Features',
  785. 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>',
  786. focusConfirm: false,
  787. allowOutsideClick: 'cancel',
  788. showCancelButton: true,
  789. preConfirm: () => {
  790. let selectedSubFeatures = [];
  791. const checkboxes = document.querySelectorAll('.swal2-input[type="checkbox"]:checked');
  792. checkboxes.forEach((checkbox) => {
  793. selectedSubFeatures.push(checkbox.value);
  794. });
  795. if (selectedSubFeatures.length === 0) {
  796. Swal.showValidationMessage('Please select at least one feature');
  797. }
  798.  
  799. return selectedSubFeatures;
  800. }
  801. });
  802.  
  803.  
  804. if (selectedSubFeatures) {
  805. const features = [];
  806.  
  807. const filteredTags = taglist.filter(tag => selectedSubFeatures.includes(tag[0]));
  808.  
  809. features.push(...filteredTags);
  810. runScript(features,'')
  811. }
  812. }
  813.  
  814. else if (mainCategoriesDismiss === "cancel"){
  815. const { value: selectedAdvancedCategories, dismiss: cancelInput } = await Swal.fire({
  816. title: 'Advanced Search',
  817. html: getHtml(advancedCategories),
  818. focusConfirm: false,
  819. allowOutsideClick: false,
  820. showCancelButton: true,
  821. showCloseButton:true,
  822. cancelButtonText: 'Cancel',
  823. preConfirm: () => {
  824. selectedCategories = [];
  825. for (let category in advancedCategories) {
  826. if (document.getElementById(`swal-input-${category}`).checked) {
  827. selectedCategories.push(category);
  828. }
  829. }
  830. if (selectedCategories.length === 0) {
  831. Swal.showValidationMessage('Please select at least one option!');
  832. return false;
  833. } else if (selectedCategories.length >1) {
  834. Swal.showValidationMessage("You're only allowed to select one option!");
  835. return false;
  836. }
  837.  
  838.  
  839. return selectedCategories;
  840. }
  841. });
  842.  
  843. if (selectedAdvancedCategories) {
  844. let selectedFeatures = [];
  845. let titleText='Select Features';
  846. if (selectedAdvancedCategories.includes('Intersection')) {
  847. titleText = 'Select Major way';
  848. }
  849. for (let category of selectedAdvancedCategories) {
  850. selectedFeatures = selectedFeatures.concat(advancedCategories[category]);
  851. }
  852. const { value: selectedSubFeatures, dismiss: cancelInput } = await Swal.fire({
  853. title: titleText,
  854. 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>',
  855. focusConfirm: false,
  856. allowOutsideClick: 'cancel',
  857. showCancelButton: true,
  858. preConfirm: () => {
  859. const checkboxes = document.querySelectorAll('.swal2-input[type="checkbox"]:checked');
  860. const selectedSubFeatures = Array.from(checkboxes).map(checkbox => checkbox.value);
  861.  
  862. if (selectedSubFeatures.length < 1) {
  863. Swal.showValidationMessage('Please select at least one option!');
  864. return false;
  865. }
  866.  
  867. if (selectedAdvancedCategories.includes('Intersection')) {
  868. const minorFeatures = advancedCategories.Intersection.filter(feature => !selectedSubFeatures.includes(feature));
  869. return Swal.fire({
  870. title: 'Select Minor Way',
  871.  
  872. 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>',
  873. showCancelButton: true,
  874. preConfirm: () => {
  875. return new Promise((resolve) => {
  876. const checkboxes = document.querySelectorAll('.swal2-input[type="checkbox"]:checked');
  877. const selectedMinorFeatures = Array.from(checkboxes).map(checkbox => checkbox.value);
  878. resolve(selectedMinorFeatures);
  879. }).then((selectedMinorFeatures) => {
  880. return [selectedSubFeatures, selectedMinorFeatures];
  881. }).catch(() => {
  882. return false;
  883. });
  884. }
  885. });
  886. }
  887.  
  888. if (selectedAdvancedCategories.includes('Around Search')) {
  889. return Swal.fire({
  890. title: 'Select Around Point',
  891. html: `
  892. <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>
  893. <div>
  894. <select id="aroundPoint" class="swal2-select">
  895. ${selectedSubFeatures.map(option => `<option value="${option}">${option}</option>`)}
  896. </select>
  897. </div>
  898. <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>
  899. <div>
  900. <input type="text" id="coordinate" class="swal2-input">
  901. </div>
  902. `,
  903. showCancelButton: true,
  904. preConfirm: () => {
  905. const aroundPoint = document.getElementById('aroundPoint').value;
  906. const coordinates = document.getElementById('coordinate').value.trim();
  907.  
  908. const checkFeatures = selectedSubFeatures;
  909. const aroundPointIndex = checkFeatures.indexOf(aroundPoint);
  910. checkFeatures.splice(aroundPointIndex, 1);
  911. const hasRealationFeature = checkFeatures.some(feature => mapFeatures.relation.includes(feature));
  912. if (hasRealationFeature) {
  913. Swal.showValidationMessage('Realtion type of points must be set as around point!Better select only one relation type of feature.');
  914. return false;
  915. }
  916. if (isNaN(coordinates) ||selectedSubFeatures.length===1) {
  917. Swal.showValidationMessage('Please enter a coordinate or select more than 2 features!');
  918. return false;
  919.  
  920. if (coordinates) {
  921. const [latitude, longitude] = coordinates.split(',').map(coord => parseFloat(coord.trim()));
  922.  
  923. if (isNaN(latitude) || isNaN(longitude)) {
  924. Swal.showValidationMessage('Please enter a valid coordinate!');
  925. return false;
  926. }
  927. }
  928.  
  929. return Swal.fire({
  930. title: 'Please enter a radius(metre)',
  931. input: 'text',
  932. inputLabel: 'Radius',
  933. inputPlaceholder: 'Enter radius...',
  934. showCancelButton: true,
  935. inputValue: 100,
  936. inputValidator: (value) => {
  937. const radiusValue = parseInt(value);
  938. if (isNaN(radiusValue) || radiusValue < 10 || radiusValue > 10000) {
  939. return 'Please enter a valid integer between 10 and 10000!';
  940. }
  941. }
  942. }).then((result) => {
  943. if (result.isConfirmed) {
  944. const radius = result.value;
  945. return [selectedSubFeatures, radius, [latitude, longitude]];
  946. } else {
  947. return false;
  948. }
  949. });
  950. } else {
  951. return Swal.fire({
  952. title: 'Please enter a radius(metre)',
  953. input: 'text',
  954. inputLabel: 'Radius',
  955. inputPlaceholder: 'Enter radius...',
  956. showCancelButton: true,
  957. inputValue: 100,
  958. inputValidator: (value) => {
  959. const radiusValue = parseInt(value);
  960. if (isNaN(radiusValue) || radiusValue < 10 || radiusValue > 10000) {
  961. return 'Please enter a valid integer between 10 and 10000!';
  962. }
  963. }
  964. }).then((result) => {
  965. if (result.isConfirmed) {
  966. const radius = result.value;
  967. return [selectedSubFeatures, aroundPoint, radius];
  968. } else {
  969. return false;
  970. }
  971. });
  972. }
  973. }
  974. });
  975. }
  976.  
  977. else{return selectedSubFeatures}}
  978.  
  979. });
  980. if (selectedSubFeatures) {
  981.  
  982. const features = [];
  983. let filteredTags;
  984. if (selectedAdvancedCategories.includes('Intersection')){
  985. const intersectionFeatures=selectedSubFeatures.value
  986. let majorTags
  987. let minorTags
  988.  
  989. features.push(...[intersectionFeatures[0]],...[intersectionFeatures[1]]);
  990.  
  991. runScript(features,'Intersection')
  992. }
  993. if (selectedAdvancedCategories.includes('Around Search')){
  994. let aroundFeatures=selectedSubFeatures.value
  995. filteredTags = taglist.filter(tag => aroundFeatures[0].includes(tag[0]))
  996. features.push(...filteredTags)
  997. if (Array.isArray(aroundFeatures[2])) {
  998. getData('','coordinate',[features,aroundFeatures[1],aroundFeatures[2]],'Around')}
  999. else{
  1000. runScript([features,aroundFeatures[1],aroundFeatures[2]],'Around')}
  1001. }
  1002. }
  1003. }
  1004. }
  1005. }
  1006.  
  1007. async function downloadGeoJSONFromOSMID(f,a) {
  1008. Swal.fire({
  1009. title: 'Enter OSM ID or place name',
  1010. input: 'text',
  1011. inputValue: 'Paris or 71525',
  1012. showCancelButton: true,
  1013. confirmButtonText: 'Submit',
  1014. cancelButtonText: 'Cancel',
  1015. showCloseButton:true,
  1016. inputValidator: (value) => {
  1017. if (!value) {
  1018. return 'You need to enter something!';
  1019. }
  1020. }
  1021. }).then(async (result) => {
  1022. if (result.isConfirmed) {
  1023. const userInput = result.value;
  1024. if (!isNaN(userInput)) {
  1025. await downloadGeoJSON(userInput);
  1026. } else {
  1027. try {
  1028. const osmID = await getOSMID(userInput);
  1029. if (osmID) {
  1030. await downloadGeoJSON(osmID);
  1031. if(f||a){
  1032. setTimeout(function() {promptInput(f,a)},500)
  1033. }
  1034. } else {
  1035. Swal.fire('Error', 'OSM ID not found for the provided place name.', 'error');
  1036. }
  1037. } catch (error) {
  1038. console.error('Error:', error);
  1039. }
  1040. }
  1041. } else if (result.dismiss === Swal.DismissReason.cancel) {
  1042. console.log('No input provided.');
  1043. }
  1044. });}
  1045.  
  1046. async function getOSMID(placeName) {
  1047. const nominatimURL = `https://nominatim.openstreetmap.org/search?format=json&q=${placeName}&addressdetails=1`;
  1048. const response = await fetch(nominatimURL, {
  1049. headers: {
  1050. 'Accept-Language': 'en-US,en;q=0.9'
  1051. }
  1052. });
  1053. const data = await response.json();
  1054. if (data.length > 0) {
  1055. let options = {};
  1056. for (let i = 0; i < Math.min(5, data.length); i++) {
  1057. options[i + 1] = `${data[i].display_name}\n${data[i].address.country}`;
  1058. }
  1059.  
  1060. const { value: chosenIndex } = await Swal.fire({
  1061. title: "Choose a location",
  1062. input: 'select',
  1063. inputOptions: options,
  1064. showCancelButton:true,
  1065. inputValidator: (value) => {
  1066. if (value === '') {
  1067. return 'You must select a location';
  1068. }
  1069. }
  1070. });
  1071.  
  1072. if (chosenIndex !== '') {
  1073. const index = parseInt(chosenIndex);
  1074. return data[index - 1].osm_id;
  1075. } else {
  1076. return null;
  1077. }
  1078. } else {
  1079. return null;
  1080. }
  1081. }
  1082.  
  1083. async function downloadGeoJSON(osmID) {
  1084. const url = `https://polygons.openstreetmap.fr/get_geojson.py?id=${osmID}`;
  1085.  
  1086. try {
  1087. const response = await fetch(url);
  1088. if (!response.ok) {
  1089. throw new Error('Failed to fetch GeoJSON data.');
  1090. }
  1091.  
  1092. const data = await response.json();
  1093. const geojsonString = JSON.stringify(data);
  1094. const blob = new Blob([geojsonString], { type: 'application/json' });
  1095. const link = document.createElement('a');
  1096. link.href = window.URL.createObjectURL(blob);
  1097. link.download = `osm_boundary_${osmID}.geojson`;
  1098. link.click();
  1099. } catch (error) {
  1100. console.error('Error downloading GeoJSON:', error);
  1101. alert('Error downloading GeoJSON')
  1102. }
  1103. }
  1104.  
  1105.  
  1106. var triggerButton = document.createElement("button");
  1107. triggerButton.innerHTML = "Feature Your Map";
  1108. triggerButton.style.position = 'absolute';
  1109. triggerButton.style.right = '10px';
  1110. triggerButton.style.top = '8px';
  1111. triggerButton.style.width='160px'
  1112. triggerButton.style.fontSize='16px'
  1113. triggerButton.style.borderRadius = "16px";
  1114. triggerButton.style.padding = "5px 5px";
  1115. triggerButton.style.border = "none";
  1116. triggerButton.style.backgroundColor = "#4CAF50";
  1117. triggerButton.style.color = "white";
  1118. triggerButton.style.cursor = "pointer";
  1119. document.body.appendChild(triggerButton);
  1120.  
  1121. var button = document.createElement('button');
  1122. button.textContent = 'Download GeoJSON'
  1123. button.style.position = 'absolute';
  1124. button.style.right = '180px';
  1125. button.style.top = '8px';
  1126. button.style.width='160px'
  1127. button.style.fontSize='16px'
  1128. button.style.borderRadius = "16px";
  1129. button.style.padding = "5px 5px";
  1130. button.style.border = "none";
  1131. button.style.backgroundColor = "#4CAF50";
  1132. button.style.color = "white";
  1133. button.style.cursor = "pointer";
  1134. document.body.appendChild(button);
  1135.  
  1136. var settingButton = document.createElement('button');
  1137. settingButton.textContent = 'Default Setting'
  1138. settingButton.style.position = 'absolute';
  1139. settingButton.style.right = '350px';
  1140. settingButton.style.top = '8px';
  1141. settingButton.style.width='160px'
  1142. settingButton.style.fontSize='16px'
  1143. settingButton.style.borderRadius = "16px";
  1144. settingButton.style.padding = "5px 5px";
  1145. settingButton.style.border = "none";
  1146. settingButton.style.backgroundColor = "#4CAF50";
  1147. settingButton.style.color = "white";
  1148. settingButton.style.cursor = "pointer";
  1149. document.body.appendChild(settingButton);
  1150.  
  1151. settingButton.addEventListener('click', getSettings)
  1152. button.addEventListener('click', downloadGeoJSONFromOSMID);
  1153. triggerButton.addEventListener("click", getFeatures);
  1154. })();