├── .nojekyll ├── .prettierignore ├── .prettierrc.json ├── ARCHITECTURE.md ├── CONTRIBUTING.md ├── COPYING ├── README.md ├── examples ├── general_high_zoom.html ├── general_high_zoom_-_airport.png ├── general_high_zoom_-_road_crossing.png ├── general_high_zoom_map_style.js ├── generated │ ├── general_high_zoom_taginfo.json │ ├── laser_neighbourhood_taginfo.json │ └── laser_road_area_taginfo.json ├── laser_neighbourhood.html ├── laser_neighbourhood_-_Madalińskiego.png ├── laser_neighbourhood_map_style.js ├── laser_road_area.html ├── laser_road_area_map_style.js ├── laser_road_area_prototype_delivered_cropped.jpg ├── lunar_assembler.dist.css ├── lunar_assembler.dist.js ├── lunar_assembler_in_action.gif ├── simple.html ├── simple_map_style_-_Sztynort.png └── taginfo_file_generate.js ├── images_for_description └── logo_osmf.png ├── lunar_assembler.js ├── lunar_assembler_dependencies ├── COPYING ├── d3-v5.16.0.js ├── leaflet-draw.v-zombie-bleeding-edge-latest-commit-with-manually-inlined-rectangle-svg.css ├── leaflet-draw.v-zombie-bleeding-edge-latest-commit.js ├── leaflet-v0.7.js ├── leaflet-version0.7_references_to_layer_switcher_image_commented_out.css ├── osmtogeojson.js ├── polygon-clipping.umd.js └── turf.min.js ├── lunar_assembler_helpful_functions_for_map_styles.js ├── lunar_assembler_helpful_functions_for_map_styles_apply_patterns_to_areas.js ├── lunar_assembler_helpful_functions_for_map_styles_generate_inaccessible_areas.js ├── lunar_assembler_helpful_functions_for_map_styles_generate_symbolic_steps_from_area_highway.js ├── lunar_assembler_helpful_functions_for_map_styles_generate_symbolic_zebra_bars.js ├── lunar_assembler_helpful_functions_for_map_styles_openstreetmap_tagging_knowledge.js ├── lunar_assembler_helpful_functions_for_map_styles_unified_styling_handler.js ├── npm_replacement.py └── tests └── run_tests.js /.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matkoniecz/lunar_assembler/45fb1e623f78b23e13fc8e60f660b51ef6736f59/.nojekyll -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | lunar_assembler_dependencies 2 | examples/lunar_assembler.dist.css 3 | examples/lunar_assembler.dist.js 4 | *.html -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /ARCHITECTURE.md: -------------------------------------------------------------------------------- 1 | # Why it exists 2 | 3 | I needed SVG generator from OpenStreetMap data that would be accessible to anyone, without requirements to install anything new. 4 | 5 | Generated SVG must be usable as design files for laser cutter, for a [specific project](https://wiki.openstreetmap.org/wiki/Microgrants/Microgrants_2020/Proposal/Tactile_maps_for_blind_or_visually_impaired_children) (tactile maps for blind or visually impaired children). 6 | 7 | # Fundamentals 8 | 9 | Obtains OpenStreetMap data and generates SVG map from that. 10 | 11 | It must run as client side JS hosted on static page. Server-side solution is rejected as neither needed nor viable here. Serving it as a static site has a nice side-effect of making potential reuse easier. 12 | 13 | Performance was not considered during design, as main target of work would involve tiny areas. It is also intended as a feasibility test to check is it possible at all, and to identify what is bottleneck. 14 | 15 | # Alternatives 16 | 17 | There are some [known tools](https://wiki.openstreetmap.org/wiki/SVG) for that, but none working as client-side JS. 18 | 19 | Some were potentially adaptable, but ignored due to having too interesting architecture. For example [Osmarender](https://wiki.openstreetmap.org/wiki/Osmarender/Convert_osm_data_from_OSM_file_to_an_SVG_image) using XSLT to transform OSM XML into SVG XML or compiling my Python scripts to JS using [pyodide](https://github.com/pyodide/pyodide) was considered and discarded. 20 | 21 | # Structure 22 | 23 | ## GUI for triggerring generation 24 | 25 | GUI for selecting rectangle that will be processed - Leaflet + Leaflet-draw plugin. 26 | 27 | ## Obtaining OSM data 28 | 29 | Gets bounding box as an argument, returns geojson for further processing. 30 | 31 | Fetching OpenStreeMap data - [Overpass API](https://wiki.openstreetmap.org/wiki/Overpass_API) 32 | 33 | OpenStreetMap data from the Overpass into geojson - [osmtogeojson](http://tyrasd.github.io/osmtogeojson/) 34 | 35 | # Generating SVG 36 | 37 | One extra step is needed because d3 is extraspecial and is deliberately breaking RFC 7946. So it is necessary to rewind all geometries to follow left-hand rule rather than right-hand rule. And d3 for now has this trap rather than fix it and release breaking update that would [fix this mess](https://github.com/d3/d3-geo/pull/79#issuecomment-281031437). (to be fair D3 predates RFC 7946 - but doing thing reverse than anyone else is an annoying trap) 38 | 39 | Rendering geojson data as SVG is done using [d3.js](https://d3js.org/). 40 | 41 | Allowing to download that SVG is fairly simple once SVG is part of web page. 42 | 43 | # Why JS? 44 | 45 | I started writing it as Python script, before realising that intended audience should not be assumed to be programmers (unlike say [library for automating OSM edits](https://github.com/matkoniecz/osm_bot_abstraction_layer)). 46 | 47 | So CLI installed as Python module would be a massive barrier for potential users, GUI would be really problematic. Even with self-contained installer people would still need to install something - again, a significant barrier. 48 | 49 | Still, people would need to install random executable. 50 | 51 | Visiting page in browser is significantly easier to use and has much lower barrier. 52 | 53 | # npm rant 54 | 55 | My dislike toward JS was significantly reduced while writing this - maybe even eliminated. 56 | 57 | My dislike toward `npm` ecosystem got confirmed, at least for now. The `npm_attempt` branch was so far massive time sink, I wasted a lot of time and I still have no working replacement. Note: while it may be a TypeScript fault or my failure to find a proper documentation... I tried really hard how to make something that would allow me to 58 | 59 | - specify dependencies 60 | - allow to easily update them 61 | - generate .js bundle from that 62 | - - skipping unused code from libraries is strongly preferable, but that is optional 63 | - do some code linting/autoformatting 64 | 65 | And I failed completely. Note that it is likely problem is not caused by my incopetence. I figured how to do it in Ruby and in Python, with just documentation that I found online. And here I failed even with external help (so far at least!). 66 | 67 | Note: if someone is able to point out how can I get working `leaflet-draw` (or equivalent) in TypeScript I would be thankful ( current attempt state in [https://github.com/matkoniecz/lunar_assembler/commits/npm_attempt](https://github.com/matkoniecz/lunar_assembler/commits/npm_attempt) ). 68 | 69 | Similarly if someone knows sane way to have simple working method to manage `.js` dependencies I would appreciate it. 70 | 71 | Fun fact: [https://docs.npmjs.com/getting-started](https://docs.npmjs.com/getting-started) has info how to upgrade to a paid account but no info how to do accomplish fundamental actually useful tasks with `npm`. 72 | 73 | [https://docs.npmjs.com/using-npm-packages-in-your-projects](https://docs.npmjs.com/using-npm-packages-in-your-projects) has 74 | 75 | > Once you have installed a package in node_modules, you can use it in your code. 76 | 77 | what explain exactly nothing how the heck I should do it 78 | 79 | # Feedback 80 | 81 | If you looked here to find something and it is not present - please open an issue. 82 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # To rebuild code you need to run npm_replacement.py script! 2 | 3 | Use `python3 npm_replacement.py` to do that. 4 | 5 | # Welcome! 6 | 7 | Contributions are highly welcomed! 8 | 9 | Bug reports, pull requests are welcomed and invited. 10 | 11 | For bigger changes I strongly encourage to create an issue first to review the idea. 12 | 13 | Issues with scathing criticism of code quality are also welcomed, as long as you are specific and clear what is wrong and how it can be improved. 14 | 15 | # License 16 | 17 | Note that by contributing code you are licensing it to license used by this repository (AGPL). 18 | 19 | # Installing development dependencies 20 | 21 | `sudo npm install -g prettier` 22 | 23 | # Detect code style issues 24 | 25 | ???? 26 | 27 | TODO: find JS linter 28 | 29 | # Automatically reformat code to follow some sane coding style 30 | 31 | `npx prettier --print-width 200 --write .` 32 | 33 | Sadly, it is necessary to treat it as suggestions. 34 | 35 | I failed to find for now to: 36 | 37 | - allow multiline array definition, with one element per line 38 | - avoid pointless splitting if statements into multiple lines just because limit of 80 characters was reached 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lunar assembler 2 | 3 | SVG maps from OpenStreetMap data in browser, can be used by marking area on a map. Using it does not require any programming skills. 4 | 5 | ## In action 6 | 7 | ### General high zoom map style 8 | 9 | [Publicly accessible generation of any location in this map style](https://mapsaregreat.com/osm_to_svg_in_browser/general_high_zoom) 10 | 11 | ![examples/general_high_zoom_-_road_crossing.png](examples/general_high_zoom_-_road_crossing.png) 12 | 13 | #### Animation of a typical use 14 | 15 | ![examples/lunar_assembler_in_action.gif](examples/lunar_assembler_in_action.gif) 16 | 17 | ![examples/general_high_zoom_-_airport.png](examples/general_high_zoom_-_airport.png) 18 | 19 | [try it!](https://mapsaregreat.com/osm_to_svg_in_browser/) 20 | 21 | ### Generating design for laser cut tactile maps 22 | 23 | ![examples/laser_neighbourhood_-_Madalińskiego.png](examples/laser_neighbourhood_-_Madalińskiego.png) 24 | 25 | This map styles are designed for used in a laser cutter, to produce maps for blind. 26 | 27 | [Ultra-high zoom map for showing a single crossing](https://mapsaregreat.com/osm_to_svg_in_browser/laser_road_area), relying on area:highway. 28 | 29 | [high zoom map style for showing neighbourhood, with some attempts to automatically simplify data, no need for area:highway](https://mapsaregreat.com/osm_to_svg_in_browser/laser_neighbourhood). 30 | 31 | #### Laser cut 3D tactile map 32 | 33 | 34 | 35 | 3D tactile map for blind children. Teaching aid for orientation lessons - special subject where children learn how to move on their own across the city. This is a map of a specific crossing, nearby their school. Made using this project, this propotype was donated to the school. 36 | 37 | Cut using file generated by [map style shown in the section above](https://mapsaregreat.com/osm_to_svg_in_browser/laser_road_area). 38 | 39 |
40 | 41 | ## How can I use it on my site? 42 | 43 | This work is AGPL licensed. This means that you also can use it, with requirement to share your improvements (see license for the full info, this is a single-sentence summary). 44 | 45 | [examples](examples) folder contains files necessary to use that in you project: 46 | 47 | - `lunar_assembler.dist.js` (build .js file containing all necessary JS code) 48 | - `lunar_assembler.dist.css` (build .js file containing all necessary CSS code) 49 | 50 | Feel free to use it in your own projects or build on it! 51 | 52 | Pull requests with improvements are also welcomed! 53 | 54 | ### How can I make own map style? 55 | 56 | Map style can be quite simple - just setting colors for lines/areas. 57 | 58 | You can make own map style and use it at your own site. 59 | 60 | You will need to copy [examples/lunar_assembler.dist.css](examples/lunar_assembler.dist.css) and [examples/lunar_assembler.dist.js](examples/lunar_assembler.dist.js) files and include it in your own site. 61 | 62 | Fully functional examples are in an [example](example/) folder. 63 | 64 | There is a very simple but also working map style that can show water area, forests, buildings and marinas. See [this simple example](https://github.com/matkoniecz/lunar_assembler/blob/master/examples/simple.html#L59), this page is hosted and shown [here](https://mapsaregreat.com/osm_to_svg_in_browser/simple). 65 | 66 | ![examples/simple_map_style_-_Sztynort.png](examples/simple_map_style_-_Sztynort.png) 67 | 68 | #### Advanced map styles 69 | 70 | In some cases one needs to somehow deals with layers. For example, what should be displayed if road in tunnel goes under building? Or under forest? What should be shown if there is a lake in a forest? 71 | 72 | The most complex part of default map style is dealing with layers and it is often quite tricky. 73 | 74 | Map styles also have access to a powerful functionality allowing complete rewriting of geometries, what is necessary to implement some functionality but typically can be ignored. I used it for example to generate zebra crossing bars on detailed road maps and to generate symbolic representations of steps areas. 75 | 76 | ### Help! 77 | 78 | Note: especially this part of documentation is incomplete and would benefit from better info. Please [open an issue](https://github.com/matkoniecz/lunar_assembler/issues/new) and mention what is confusing and where you are stuck. Right now I am not entirely sure what kind of knowledge people using it will have and what and how should be explained. 79 | 80 | ## Other published styles 81 | 82 | Demonstration of [basic high-zoom map style](https://mapsaregreat.com/osm_to_svg_in_browser/general_high_zoom) 83 | 84 | ## Architecture 85 | 86 | It works in following way 87 | 88 | - user selects an area 89 | - Overpass API is called to download OpenStreetMap data in that area 90 | - data is locally processed and rendered 91 | 92 | it means that servers only delivers HTML site, code then runs on client. It still needs access to Overpass API but there are generously provided servers allowing some limited use. 93 | 94 | ### Use on other sites 95 | 96 | [current release and (for now only known use of it)](https://mapsaregreat.com/osm_to_svg_in_browser/) 97 | 98 | ## Potential uses 99 | 100 | SVG files may be much more accessible for further processing than alternatives formats of OSM data. 101 | 102 | SVG files may be directly usable for some purposes, for example in laser cutters. 103 | 104 | ## Mentions of use are welcome 105 | 106 | In case that you used this code or it inspired you to do something - feel free to create an issue with photo/description of what was produced! Or send an email to [matkoniecz@tutanota.com](mailto:matkoniecz@tutanota.com). It would be nice to have confirmation that publishing it was useful for somebody. 107 | 108 | ## Improving documentation 109 | 110 | Please create a new issue if you want to use it but current instructions are insufficient, wrong or can be in some way improved! 111 | 112 | I know that documentation may be far better, but I am not sure what kind of additional documentation would be most useful. 113 | 114 | ## Alternatives 115 | 116 | As usual [OSM Wiki documentation](https://wiki.openstreetmap.org/wiki/SVG#Ways_to_create_an_SVG_map_from_OpenStreetMap) is useful - there are also other ways to [make SVG maps from OpenStreetMap data](https://wiki.openstreetmap.org/wiki/SVG#Ways_to_create_an_SVG_map_from_OpenStreetMap). 117 | 118 | [https://touch-mapper.org/en/](Touch Mapper) is another OSM based tool for making tactile maps. It differs by relying on a 3D printing and by more complex infrastructure. 119 | 120 | It requires Amazon Web Services to run. Inability to put limit on spending there scared me away from basing my work on it. I do not accept idea that bug in my code may result in a 100 000$ bill that may or may not be waived. 121 | 122 | I have run programs that tried to [allocate 4654951TB of memory](https://github.com/a-b-street/abstreet/issues/148) due to a bad configuration and I am scared that Amazon would allow this to happen and then bill me. 123 | 124 | [hapticke.mapy.cz](https://hapticke.mapy.cz/?lang=en) may be useful if you need tactile maps. 125 | 126 | ## Skipped and rejected features 127 | 128 | Note that it is basically impossible to make SVGs that would qualify as high-quality cartography here. As reason why this project exists does not require it (generation of laser cutter designs) it was not considered during design and implementation. But feel free to open issues and create pull requests to add missing functionality! 129 | 130 | ## Sponsors 131 | 132 |
133 | 134 | The [OpenStreetMap foundation](https://wiki.osmfoundation.org/wiki/Main_Page) was funding the development of this project in their first round of the [microgrant program](https://wiki.osmfoundation.org/wiki/Microgrants) in 2020. It was done as part of making [tactile maps based on OpenStreetMap data, for blind or visually impaired children](https://wiki.openstreetmap.org/wiki/Microgrants/Microgrants_2020/Proposal/Tactile_maps_for_blind_or_visually_impaired_children) (part of making used tools accessible to other and OpenStreetMap promotion). 135 | 136 | If anyone else is also interested in supporting this project via funding - [let me know](mailto:osm-messages@etutanota.com) (opening a new issue is also OK) and it is likely that some way of doing that can be found :) 137 | -------------------------------------------------------------------------------- /examples/general_high_zoom.html: -------------------------------------------------------------------------------- 1 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | General high zoom - SVG map from OpenStreetMap data 25 | 29 | 30 | 31 | 32 |
33 |
34 |

Press the rectangle button, select a rectangle on a map and wait. Note that this tool will work well with small areas. Entire cities may work, but will require long processing. Processing entire countries will fail.

35 | 36 |

Generation of vector maps from OpenStreetMap data. Supports area:highway tagging, works well for tiny areas (single crossing), neighbourhood maps - or of a single forest/airport/etc. Will work for entire town, villages - maybe even entire cities.

37 | 38 |

See also other map styles, allowing generation of other maps from the same OpenStreetMap data.

39 | 40 |

(there is more text below map, you can also see an animation showing how this tool is supposed to work)

41 | 42 |
43 |
44 | 45 |
46 |
47 | 48 |

49 |

56 |

57 | 58 |

Map style has support for areas such as buildings, water, parks, forests, road areas and linear features such as roads, railways, waterways, taxiway, runways. Produced SVG files can be downloaded.

59 | 60 |

Examples

61 |
62 | please send an email to matkoniecz@tutanota.com if replacing this placeholder alt attribute of image by a real alt attribute would be useful for you 63 |

Example of a map generated from OpenStreetMap data, on ODbL license.

64 |
65 |
66 | please send an email to matkoniecz@tutanota.com if replacing this placeholder alt attribute of image by a real alt attribute would be useful for you 67 |

Example of a map generated from OpenStreetMap data, on ODbL license.

68 |
69 | 70 |

Even more info

71 |

Have fun with using OpenStreetMap data for interesting and/or useful purposes! Just remember that you must mention the source of data in a way visible to whoever will be using it. See this page for details, including cases where it is legally allowed to avoid giving a clear credit (but I encourage to do this in all cases).

72 | 73 |

If you liked this tool, something was confusing - you are welcomed to comment about it by creating a public issue or by sending me an email. You can also post a comment in OSM diary entry.

74 | 75 |

If you want to help mapping and you are unsure how to start - you can try StreetComplete (an Android app) allowing contributions, with sole requirements being (1) ability to read (2) ability to see things (3) ability to create an OpenStreetMap account.

76 | 77 |

If you want to map something (map missing objects, fix a mistake) and you are unsure how to start - visit openstreetmap.org, zoom in to interesting area and press the "edit" button and follow instructions. You can contact the OpenStreetMap community using one of global or local channels listed at community.osm.be and ask for help.

78 | 79 |

You can visit matkoniecz/lunar_assembler to see code, obtain code or contribute. This software is AGPLv3 licenced.

80 | 81 |

Legend

82 | 83 |

84 | 85 | 86 |
87 | 88 | 89 | 90 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /examples/general_high_zoom_-_airport.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matkoniecz/lunar_assembler/45fb1e623f78b23e13fc8e60f660b51ef6736f59/examples/general_high_zoom_-_airport.png -------------------------------------------------------------------------------- /examples/general_high_zoom_-_road_crossing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matkoniecz/lunar_assembler/45fb1e623f78b23e13fc8e60f660b51ef6736f59/examples/general_high_zoom_-_road_crossing.png -------------------------------------------------------------------------------- /examples/general_high_zoom_map_style.js: -------------------------------------------------------------------------------- 1 | /* 2 | lunar_assembler - tool for generating SVG files from OpenStreetMap data. Available as a website. 3 | Copyright (C) 2021 Mateusz Konieczny 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Affero General Public License as 7 | published by the Free Software Foundation, under version 3 of the 8 | License only. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU Affero General Public License for more details. 14 | 15 | You should have received a copy of the GNU Affero General Public License 16 | along with this program. If not, see . 17 | */ 18 | function highZoomMapStyle() { 19 | var mapStyle = { 20 | paintOrder(feature) { 21 | // higher values: more on top 22 | 23 | var valueRangeForOneLayer = 10000; 24 | var layer = 0; 25 | if (feature.properties["layer"] != null) { 26 | /* 27 | ignore layer tag on buildings and similar 28 | to discourage tagging for renderer 29 | note that undeground buildings are later skipped 30 | */ 31 | if ( 32 | feature.properties["building"] == null && 33 | (feature.properties["natural"] == null || feature.properties["natural"] == "water") && 34 | feature.properties["landuse"] == null && 35 | feature.properties["leisure"] == null 36 | ) { 37 | layer = feature.properties["layer"]; 38 | } 39 | } 40 | 41 | if (railwayLinearValuesArray().includes(feature.properties["railway"])) { 42 | var priority = 0.99; 43 | return valueRangeForOneLayer * priority + valueRangeForOneLayer * layer; 44 | } 45 | if (feature.properties["area:highway"] != null) { 46 | var priority = 0.98; 47 | return valueRangeForOneLayer * priority + valueRangeForOneLayer * layer; 48 | } 49 | if (feature.properties["building"] != null && feature.properties["location"] != "underground") { 50 | var priority = 0.95; 51 | return valueRangeForOneLayer * priority + valueRangeForOneLayer * layer; 52 | } 53 | if (feature.properties["aeroway"] == "runway" || feature.properties["aeroway"] == "taxiway") { 54 | var priority = 0.94; 55 | return valueRangeForOneLayer * priority + valueRangeForOneLayer * layer; 56 | } 57 | if (feature.properties["barrier"] != null) { 58 | var priority = 0.9; 59 | return valueRangeForOneLayer * priority + valueRangeForOneLayer * layer; 60 | } 61 | if (feature.properties["highway"] != null) { 62 | var priority = 0.85; 63 | return valueRangeForOneLayer * priority + valueRangeForOneLayer * layer; 64 | } 65 | if (feature.properties["barrier"] != null) { 66 | var priority = 0.7; 67 | return valueRangeForOneLayer * priority + valueRangeForOneLayer * layer; 68 | } 69 | if (feature.properties["man_made"] === "bridge") { 70 | var priority = 0.65; 71 | return valueRangeForOneLayer * priority + valueRangeForOneLayer * layer; 72 | } 73 | if (feature.properties["waterway"] != null) { 74 | /* render waterway lines under bridge areas */ 75 | var priority = 0.6; 76 | return valueRangeForOneLayer * priority + valueRangeForOneLayer * layer; 77 | } 78 | if (feature.properties["natural"] === "water" || feature.properties["waterway"] === "riverbank") { 79 | // render natural=wood below natural=water 80 | var priority = 0.1; 81 | return valueRangeForOneLayer * priority + valueRangeForOneLayer * layer; 82 | } 83 | if (feature.properties["natural"] === "bare_rock") { 84 | // render natural=wood below natural=bare_rock 85 | // render water rather than underwater rocks 86 | var priority = 0.05; 87 | return valueRangeForOneLayer * priority + valueRangeForOneLayer * layer; 88 | } 89 | if (feature.properties["leisure"] != null) { 90 | // render leisure=park below natural=water or natural=wood 91 | // but above landuse=residential 92 | var priority = 0.03; 93 | return valueRangeForOneLayer * priority + valueRangeForOneLayer * layer; 94 | } 95 | if (feature.properties["landuse"] != null || feature.properties["aeroway"] == "aerodrome") { 96 | //better higher and trigger layering problems quickly that have something failing ONLY in parks 97 | var priority = 0.02; 98 | return valueRangeForOneLayer * priority + valueRangeForOneLayer * layer; 99 | } 100 | return valueRangeForOneLayer * layer; 101 | }, 102 | 103 | unifiedStyling() { 104 | returned = []; 105 | var i = motorizedRoadValuesArray().length; 106 | while (i--) { 107 | value = motorizedRoadValuesArray()[i]; 108 | returned.push({ 109 | area_color: "#555555", 110 | description: "area of a motorized road (linear representation must be also present! Using only area representation is invalid!)", 111 | matches: [{ key: "area:highway", value: value }], 112 | }); 113 | returned.push({ 114 | line_color: "#555555", 115 | line_width: 2, 116 | description: "linear representation of a motorized road", 117 | matches: [{ key: "highway", value: value }], 118 | }); 119 | } 120 | 121 | var i = railwayLinearValuesArray().length; 122 | while (i--) { 123 | value = railwayLinearValuesArray()[i]; 124 | returned.push({ 125 | line_color: "black", 126 | line_width: 2, 127 | description: "linear representation of a single railway track", 128 | matches: [{ key: "railway", value: value }], 129 | }); 130 | } 131 | 132 | var i = pedestrianWaysValuesArray().length; 133 | while (i--) { 134 | value = pedestrianWaysValuesArray()[i]; 135 | returned.push({ 136 | area_color: "#aaaaaa", 137 | description: "area of a pedestrian way (linear representation must be also present! Using only area representation is invalid!)", 138 | matches: [{ key: "area:highway", value: value }], 139 | }); 140 | returned.push({ 141 | line_color: "#aaaaaa", 142 | line_width: 1, 143 | description: "linear representation of a pedestrian way", 144 | matches: [{ key: "highway", value: value }], 145 | }); 146 | } 147 | 148 | var barriersKeyValue = []; 149 | var i = linearGenerallyImpassableBarrierValuesArray().length; 150 | while (i--) { 151 | value = linearGenerallyImpassableBarrierValuesArray()[i]; 152 | barriersKeyValue.push({ key: "barrier", value: value }); 153 | } 154 | 155 | returned.push({ 156 | line_color: "black", 157 | line_width: 1, 158 | description: "linear, generally impassable barrier", 159 | matches: barriersKeyValue, 160 | }); 161 | 162 | returned.push( 163 | ...[ 164 | { 165 | area_color: "#aaaaaa", 166 | description: "pedestrian square (using it for sidewalk areas is invalid!)", 167 | matches: [ 168 | [ 169 | { key: "highway", value: "pedestrian" }, 170 | { key: "area", value: "yes", role: "supplementary_obvious_filter" }, 171 | ], 172 | [ 173 | { key: "highway", value: "pedestrian" }, 174 | { key: "type", value: "multipolygon", role: "supplementary_obvious_filter" }, 175 | ], 176 | ], 177 | }, 178 | { 179 | area_color: "#555555", 180 | description: "road area of a taxi stop (used in addition to amenity=taxi)", 181 | matches: [{ key: "area:highway", value: "taxi_stop" }], 182 | }, 183 | { 184 | area_color: "#555555", 185 | description: "road area of a bus stop (used in addition to highway=bus_stop)", 186 | matches: [{ key: "area:highway", value: "bus_stop" }], 187 | }, 188 | { 189 | line_color: "#9595b4", 190 | line_width: 1, 191 | description: "linear representation of a cycleway", 192 | matches: [{ key: "highway", value: "cycleway" }], 193 | }, 194 | { 195 | area_color: "#9595b4", 196 | description: "area of a cycleway (linear representation must be also present! Using only area representation is invalid!)", 197 | matches: [{ key: "area:highway", value: "cycleway" }], 198 | }, 199 | { 200 | area_color: "#a06060", 201 | description: "pedestrian crossing through a road (area used in addition to area representing road)", 202 | matches: [{ key: "area:highway", value: "crossing" }], 203 | }, 204 | { 205 | area_color: "#bea4c1", 206 | description: "bicycle crossing through a road (area used in addition to area representing road)", 207 | matches: [{ key: "area:highway", value: "bicycle_crossing" }], 208 | }, 209 | { 210 | area_color: "blue", 211 | description: "water", 212 | matches: [ 213 | { key: "natural", value: "water" }, 214 | { key: "waterway", value: "riverbank" }, 215 | ], 216 | }, 217 | { 218 | line_color: "blue", 219 | line_width: 10, 220 | description: "linear representation of a river", 221 | matches: [{ key: "waterway", value: "river" }], 222 | }, 223 | { 224 | line_color: "blue", 225 | line_width: 7, 226 | description: "linear representation of a canal, assumed to be large", 227 | matches: [{ key: "waterway", value: "canal" }], 228 | }, 229 | { 230 | line_color: "blue", 231 | line_width: 2, 232 | description: "linear representation of a stream", 233 | matches: [{ key: "waterway", value: "stream" }], 234 | }, 235 | { 236 | line_color: "blue", 237 | line_width: 1, 238 | description: "linear representation of a ditch/drain", 239 | matches: [ 240 | { key: "waterway", value: "ditch" }, 241 | { key: "waterway", value: "stream" }, 242 | ], 243 | }, 244 | { 245 | area_color: "black", 246 | description: "buildings", 247 | matches: [{ key: "building" }], 248 | }, 249 | { 250 | area_color: "green", 251 | description: "tree-covered land", 252 | matches: [ 253 | { key: "natural", value: "wood" }, 254 | { key: "landuse", value: "forest" }, 255 | ], 256 | }, 257 | { 258 | area_color: "#efdfef", 259 | description: "part of general military-industrial land", 260 | matches: [ 261 | { key: "landuse", value: "industrial" }, 262 | { key: "landuse", value: "railway" }, 263 | { key: "landuse", value: "quarry" }, 264 | { key: "landuse", value: "construction" }, 265 | { key: "landuse", value: "military" }, 266 | { key: "aeroway", value: "aerodrome" }, 267 | ], 268 | }, 269 | { 270 | area_color: "#efefef", 271 | description: "part of general builtup land", 272 | matches: [ 273 | { key: "landuse", value: "residential" }, 274 | { key: "landuse", value: "highway" }, 275 | { key: "landuse", value: "retail" }, 276 | { key: "landuse", value: "commercial" }, 277 | { key: "landuse", value: "garages" }, 278 | { key: "landuse", value: "farmyard" }, 279 | { key: "landuse", value: "education" }, 280 | { key: "amenity", value: "school" }, 281 | { key: "amenity", value: "kidergarten" }, 282 | { key: "amenity", value: "university" }, 283 | ], 284 | }, 285 | { 286 | area_color: "#eef0d5", 287 | description: "plants on an agriculture land", 288 | matches: [ 289 | { key: "landuse", value: "farmland" }, 290 | { key: "landuse", value: "vineyard" }, 291 | { key: "landuse", value: "orchard" }, 292 | ], 293 | }, 294 | { 295 | area_color: "#c8facc", 296 | description: "recreation land", 297 | matches: [ 298 | { key: "leisure", value: "park" }, 299 | { key: "leisure", value: "pitch" }, 300 | { key: "leisure", value: "playground" }, 301 | { key: "landuse", value: "village_green" }, 302 | ], 303 | }, 304 | { 305 | area_color: "#a2ce8d", 306 | description: "vegetation that is not agriculture or forest", 307 | matches: [ 308 | { key: "landuse", value: "grass" }, 309 | { key: "landuse", value: "allotments" }, 310 | { key: "landuse", value: "meadow" }, 311 | { key: "natural", value: "grassland" }, 312 | { key: "natural", value: "scrub" }, 313 | { key: "natural", value: "heath" }, 314 | { key: "leisure", value: "garden" }, 315 | ], 316 | }, 317 | { 318 | area_color: "gray", 319 | description: "bridge outline", 320 | matches: [{ key: "man_made", value: "bridge" }], 321 | }, 322 | { 323 | area_color: "#EEE5DC", 324 | description: "bare rock", 325 | matches: [{ key: "natural", value: "bare_rock" }], 326 | }, 327 | { 328 | line_color: "purple", 329 | line_width: 5, 330 | description: "runway", 331 | matches: [{ key: "aeroway", value: "runway" }], 332 | }, 333 | { 334 | line_color: "purple", 335 | line_width: 2, 336 | description: "taxiway", 337 | matches: [{ key: "aeroway", value: "taxiway" }], 338 | }, 339 | ] 340 | ); 341 | return returned; 342 | }, 343 | 344 | fillColoring(feature) { 345 | //console.log(feature); 346 | if (["Point"].includes(feature.geometry.type)) { 347 | //no rendering of points, for start size seems to randomly differ 348 | // and leaves ugly circles - see building=* areas 349 | return "none"; 350 | } 351 | 352 | // more complex rules can be used here in addition - or instead of unified styling 353 | return getMatchFromUnifiedStyling(feature, "area_color", mapStyle.unifiedStyling()); 354 | }, 355 | 356 | strokeColoring(feature) { 357 | if (["Point"].includes(feature.geometry.type)) { 358 | //no rendering of points, for start size seems to randomly differ 359 | // and leaves ugly circles - see building=* areas 360 | return "none"; 361 | } 362 | 363 | // more complex rules can be used here in addition - or instead of unified styling 364 | 365 | return getMatchFromUnifiedStyling(feature, "line_color", mapStyle.unifiedStyling()); 366 | }, 367 | 368 | strokeWidth(feature) { 369 | // more complex rules can be used here in addition - or instead of unified styling 370 | 371 | return getMatchFromUnifiedStyling(feature, "line_width", mapStyle.unifiedStyling()); 372 | }, 373 | 374 | mergeIntoGroup(feature) { 375 | // note that points and lines are not being merged! 376 | // only areas (including multipolygins) can be merged for now 377 | // please open an issue if you need it, it increaes chance of implementation a bit 378 | // or open pull request with an implementation 379 | if (motorizedRoadValuesArray().includes(feature.properties["area:highway"])) { 380 | return "area:highway_carriageway_layer" + feature.properties["layer"]; 381 | } 382 | if ( 383 | pedestrianWaysValuesArray().includes(feature.properties["area:highway"]) || 384 | (feature.properties["highway"] == "pedestrian" && (feature.properties["area"] === "yes" || feature.properties["type"] === "multipolygon")) 385 | ) { 386 | return "area:highway_footway" + feature.properties["layer"]; 387 | } 388 | if (feature.properties["area:highway"] == "cycleway") { 389 | return "area:highway_cycleway" + feature.properties["layer"]; 390 | } 391 | return null; 392 | }, 393 | 394 | name(feature) { 395 | return feature.properties.name; 396 | }, 397 | }; 398 | return mapStyle; 399 | } 400 | -------------------------------------------------------------------------------- /examples/generated/general_high_zoom_taginfo.json: -------------------------------------------------------------------------------- 1 | { 2 | "data_format": 1, 3 | "data_url": "https://raw.githubusercontent.com/matkoniecz/lunar_assembler/master/examples/generated/general_high_zoom_taginfo.json", 4 | "project": { 5 | "name": "general SVG maps with area:highway, for zoomed in areas (city and smaller)", 6 | "description": "Map style to produce SVG maps of tiny and small areas - from single crossing to a city. Lunar Assembler map style.", 7 | "icon_url": "https://mapsaregreat.com/favicon.svg", 8 | "project_url": "https://github.com/matkoniecz/lunar_assembler", 9 | "doc_url": "https://mapsaregreat.com/osm_to_svg_in_browser/general_high_zoom", 10 | "contact_name": "Mateusz Konieczny", 11 | "contact_email": "matkoniecz@tutanota.com" 12 | }, 13 | "tags": [ 14 | { 15 | "key": "area:highway", 16 | "value": "living_street", 17 | "description": "area of a motorized road (linear representation must be also present! Using only area representation is invalid!)" 18 | }, 19 | { 20 | "key": "highway", 21 | "value": "living_street", 22 | "description": "linear representation of a motorized road" 23 | }, 24 | { 25 | "key": "area:highway", 26 | "value": "escape", 27 | "description": "area of a motorized road (linear representation must be also present! Using only area representation is invalid!)" 28 | }, 29 | { 30 | "key": "highway", 31 | "value": "escape", 32 | "description": "linear representation of a motorized road" 33 | }, 34 | { 35 | "key": "area:highway", 36 | "value": "raceway", 37 | "description": "area of a motorized road (linear representation must be also present! Using only area representation is invalid!)" 38 | }, 39 | { 40 | "key": "highway", 41 | "value": "raceway", 42 | "description": "linear representation of a motorized road" 43 | }, 44 | { 45 | "key": "area:highway", 46 | "value": "busway", 47 | "description": "area of a motorized road (linear representation must be also present! Using only area representation is invalid!)" 48 | }, 49 | { 50 | "key": "highway", 51 | "value": "busway", 52 | "description": "linear representation of a motorized road" 53 | }, 54 | { 55 | "key": "area:highway", 56 | "value": "road", 57 | "description": "area of a motorized road (linear representation must be also present! Using only area representation is invalid!)" 58 | }, 59 | { 60 | "key": "highway", 61 | "value": "road", 62 | "description": "linear representation of a motorized road" 63 | }, 64 | { 65 | "key": "area:highway", 66 | "value": "track", 67 | "description": "area of a motorized road (linear representation must be also present! Using only area representation is invalid!)" 68 | }, 69 | { 70 | "key": "highway", 71 | "value": "track", 72 | "description": "linear representation of a motorized road" 73 | }, 74 | { 75 | "key": "area:highway", 76 | "value": "service", 77 | "description": "area of a motorized road (linear representation must be also present! Using only area representation is invalid!)" 78 | }, 79 | { 80 | "key": "highway", 81 | "value": "service", 82 | "description": "linear representation of a motorized road" 83 | }, 84 | { 85 | "key": "area:highway", 86 | "value": "residential", 87 | "description": "area of a motorized road (linear representation must be also present! Using only area representation is invalid!)" 88 | }, 89 | { 90 | "key": "highway", 91 | "value": "residential", 92 | "description": "linear representation of a motorized road" 93 | }, 94 | { 95 | "key": "area:highway", 96 | "value": "unclassified", 97 | "description": "area of a motorized road (linear representation must be also present! Using only area representation is invalid!)" 98 | }, 99 | { 100 | "key": "highway", 101 | "value": "unclassified", 102 | "description": "linear representation of a motorized road" 103 | }, 104 | { 105 | "key": "area:highway", 106 | "value": "tertiary_link", 107 | "description": "area of a motorized road (linear representation must be also present! Using only area representation is invalid!)" 108 | }, 109 | { 110 | "key": "highway", 111 | "value": "tertiary_link", 112 | "description": "linear representation of a motorized road" 113 | }, 114 | { 115 | "key": "area:highway", 116 | "value": "tertiary", 117 | "description": "area of a motorized road (linear representation must be also present! Using only area representation is invalid!)" 118 | }, 119 | { 120 | "key": "highway", 121 | "value": "tertiary", 122 | "description": "linear representation of a motorized road" 123 | }, 124 | { 125 | "key": "area:highway", 126 | "value": "secondary_link", 127 | "description": "area of a motorized road (linear representation must be also present! Using only area representation is invalid!)" 128 | }, 129 | { 130 | "key": "highway", 131 | "value": "secondary_link", 132 | "description": "linear representation of a motorized road" 133 | }, 134 | { 135 | "key": "area:highway", 136 | "value": "secondary", 137 | "description": "area of a motorized road (linear representation must be also present! Using only area representation is invalid!)" 138 | }, 139 | { 140 | "key": "highway", 141 | "value": "secondary", 142 | "description": "linear representation of a motorized road" 143 | }, 144 | { 145 | "key": "area:highway", 146 | "value": "primary_link", 147 | "description": "area of a motorized road (linear representation must be also present! Using only area representation is invalid!)" 148 | }, 149 | { 150 | "key": "highway", 151 | "value": "primary_link", 152 | "description": "linear representation of a motorized road" 153 | }, 154 | { 155 | "key": "area:highway", 156 | "value": "primary", 157 | "description": "area of a motorized road (linear representation must be also present! Using only area representation is invalid!)" 158 | }, 159 | { 160 | "key": "highway", 161 | "value": "primary", 162 | "description": "linear representation of a motorized road" 163 | }, 164 | { 165 | "key": "area:highway", 166 | "value": "trunk_link", 167 | "description": "area of a motorized road (linear representation must be also present! Using only area representation is invalid!)" 168 | }, 169 | { 170 | "key": "highway", 171 | "value": "trunk_link", 172 | "description": "linear representation of a motorized road" 173 | }, 174 | { 175 | "key": "area:highway", 176 | "value": "trunk", 177 | "description": "area of a motorized road (linear representation must be also present! Using only area representation is invalid!)" 178 | }, 179 | { 180 | "key": "highway", 181 | "value": "trunk", 182 | "description": "linear representation of a motorized road" 183 | }, 184 | { 185 | "key": "area:highway", 186 | "value": "motorway_link", 187 | "description": "area of a motorized road (linear representation must be also present! Using only area representation is invalid!)" 188 | }, 189 | { 190 | "key": "highway", 191 | "value": "motorway_link", 192 | "description": "linear representation of a motorized road" 193 | }, 194 | { 195 | "key": "area:highway", 196 | "value": "motorway", 197 | "description": "area of a motorized road (linear representation must be also present! Using only area representation is invalid!)" 198 | }, 199 | { 200 | "key": "highway", 201 | "value": "motorway", 202 | "description": "linear representation of a motorized road" 203 | }, 204 | { 205 | "key": "railway", 206 | "value": "monorail", 207 | "description": "linear representation of a single railway track" 208 | }, 209 | { 210 | "key": "railway", 211 | "value": "miniature", 212 | "description": "linear representation of a single railway track" 213 | }, 214 | { 215 | "key": "railway", 216 | "value": "construction", 217 | "description": "linear representation of a single railway track" 218 | }, 219 | { 220 | "key": "railway", 221 | "value": "preserved", 222 | "description": "linear representation of a single railway track" 223 | }, 224 | { 225 | "key": "railway", 226 | "value": "light_rail", 227 | "description": "linear representation of a single railway track" 228 | }, 229 | { 230 | "key": "railway", 231 | "value": "narrow_gauge", 232 | "description": "linear representation of a single railway track" 233 | }, 234 | { 235 | "key": "railway", 236 | "value": "subway", 237 | "description": "linear representation of a single railway track" 238 | }, 239 | { 240 | "key": "railway", 241 | "value": "tram", 242 | "description": "linear representation of a single railway track" 243 | }, 244 | { 245 | "key": "railway", 246 | "value": "disused", 247 | "description": "linear representation of a single railway track" 248 | }, 249 | { 250 | "key": "railway", 251 | "value": "rail", 252 | "description": "linear representation of a single railway track" 253 | }, 254 | { 255 | "key": "area:highway", 256 | "value": "pedestrian", 257 | "description": "area of a pedestrian way (linear representation must be also present! Using only area representation is invalid!)" 258 | }, 259 | { 260 | "key": "highway", 261 | "value": "pedestrian", 262 | "description": "linear representation of a pedestrian way" 263 | }, 264 | { 265 | "key": "area:highway", 266 | "value": "steps", 267 | "description": "area of a pedestrian way (linear representation must be also present! Using only area representation is invalid!)" 268 | }, 269 | { 270 | "key": "highway", 271 | "value": "steps", 272 | "description": "linear representation of a pedestrian way" 273 | }, 274 | { 275 | "key": "area:highway", 276 | "value": "path", 277 | "description": "area of a pedestrian way (linear representation must be also present! Using only area representation is invalid!)" 278 | }, 279 | { 280 | "key": "highway", 281 | "value": "path", 282 | "description": "linear representation of a pedestrian way" 283 | }, 284 | { 285 | "key": "area:highway", 286 | "value": "footway", 287 | "description": "area of a pedestrian way (linear representation must be also present! Using only area representation is invalid!)" 288 | }, 289 | { 290 | "key": "highway", 291 | "value": "footway", 292 | "description": "linear representation of a pedestrian way" 293 | }, 294 | { 295 | "key": "barrier", 296 | "value": "fence", 297 | "description": "linear, generally impassable barrier" 298 | }, 299 | { 300 | "key": "barrier", 301 | "value": "wall", 302 | "description": "linear, generally impassable barrier" 303 | }, 304 | { 305 | "key": "barrier", 306 | "value": "hedge", 307 | "description": "linear, generally impassable barrier" 308 | }, 309 | { 310 | "key": "barrier", 311 | "value": "retaining_wall", 312 | "description": "linear, generally impassable barrier" 313 | }, 314 | { 315 | "key": "barrier", 316 | "value": "hedge_bank", 317 | "description": "linear, generally impassable barrier" 318 | }, 319 | { 320 | "key": "barrier", 321 | "value": "wire_fence", 322 | "description": "linear, generally impassable barrier" 323 | }, 324 | { 325 | "key": "barrier", 326 | "value": "city_wall", 327 | "description": "linear, generally impassable barrier" 328 | }, 329 | { 330 | "key": "barrier", 331 | "value": "guard_rail", 332 | "description": "linear, generally impassable barrier" 333 | }, 334 | { 335 | "key": "barrier", 336 | "value": "haha", 337 | "description": "linear, generally impassable barrier" 338 | }, 339 | { 340 | "key": "highway", 341 | "value": "pedestrian", 342 | "description": "pedestrian square (using it for sidewalk areas is invalid!)" 343 | }, 344 | { 345 | "key": "highway", 346 | "value": "pedestrian", 347 | "description": "pedestrian square (using it for sidewalk areas is invalid!)" 348 | }, 349 | { 350 | "key": "area:highway", 351 | "value": "taxi_stop", 352 | "description": "road area of a taxi stop (used in addition to amenity=taxi)" 353 | }, 354 | { 355 | "key": "area:highway", 356 | "value": "bus_stop", 357 | "description": "road area of a bus stop (used in addition to highway=bus_stop)" 358 | }, 359 | { 360 | "key": "highway", 361 | "value": "cycleway", 362 | "description": "linear representation of a cycleway" 363 | }, 364 | { 365 | "key": "area:highway", 366 | "value": "cycleway", 367 | "description": "area of a cycleway (linear representation must be also present! Using only area representation is invalid!)" 368 | }, 369 | { 370 | "key": "area:highway", 371 | "value": "crossing", 372 | "description": "pedestrian crossing through a road (area used in addition to area representing road)" 373 | }, 374 | { 375 | "key": "area:highway", 376 | "value": "bicycle_crossing", 377 | "description": "bicycle crossing through a road (area used in addition to area representing road)" 378 | }, 379 | { 380 | "key": "waterway", 381 | "value": "riverbank", 382 | "description": "water" 383 | }, 384 | { 385 | "key": "natural", 386 | "value": "water", 387 | "description": "water" 388 | }, 389 | { 390 | "key": "waterway", 391 | "value": "river", 392 | "description": "linear representation of a river" 393 | }, 394 | { 395 | "key": "waterway", 396 | "value": "canal", 397 | "description": "linear representation of a canal, assumed to be large" 398 | }, 399 | { 400 | "key": "waterway", 401 | "value": "stream", 402 | "description": "linear representation of a stream" 403 | }, 404 | { 405 | "key": "waterway", 406 | "value": "stream", 407 | "description": "linear representation of a ditch/drain" 408 | }, 409 | { 410 | "key": "waterway", 411 | "value": "ditch", 412 | "description": "linear representation of a ditch/drain" 413 | }, 414 | { 415 | "key": "building", 416 | "description": "buildings" 417 | }, 418 | { 419 | "key": "landuse", 420 | "value": "forest", 421 | "description": "tree-covered land" 422 | }, 423 | { 424 | "key": "natural", 425 | "value": "wood", 426 | "description": "tree-covered land" 427 | }, 428 | { 429 | "key": "aeroway", 430 | "value": "aerodrome", 431 | "description": "part of general military-industrial land" 432 | }, 433 | { 434 | "key": "landuse", 435 | "value": "military", 436 | "description": "part of general military-industrial land" 437 | }, 438 | { 439 | "key": "landuse", 440 | "value": "construction", 441 | "description": "part of general military-industrial land" 442 | }, 443 | { 444 | "key": "landuse", 445 | "value": "quarry", 446 | "description": "part of general military-industrial land" 447 | }, 448 | { 449 | "key": "landuse", 450 | "value": "railway", 451 | "description": "part of general military-industrial land" 452 | }, 453 | { 454 | "key": "landuse", 455 | "value": "industrial", 456 | "description": "part of general military-industrial land" 457 | }, 458 | { 459 | "key": "amenity", 460 | "value": "university", 461 | "description": "part of general builtup land" 462 | }, 463 | { 464 | "key": "amenity", 465 | "value": "kidergarten", 466 | "description": "part of general builtup land" 467 | }, 468 | { 469 | "key": "amenity", 470 | "value": "school", 471 | "description": "part of general builtup land" 472 | }, 473 | { 474 | "key": "landuse", 475 | "value": "education", 476 | "description": "part of general builtup land" 477 | }, 478 | { 479 | "key": "landuse", 480 | "value": "farmyard", 481 | "description": "part of general builtup land" 482 | }, 483 | { 484 | "key": "landuse", 485 | "value": "garages", 486 | "description": "part of general builtup land" 487 | }, 488 | { 489 | "key": "landuse", 490 | "value": "commercial", 491 | "description": "part of general builtup land" 492 | }, 493 | { 494 | "key": "landuse", 495 | "value": "retail", 496 | "description": "part of general builtup land" 497 | }, 498 | { 499 | "key": "landuse", 500 | "value": "highway", 501 | "description": "part of general builtup land" 502 | }, 503 | { 504 | "key": "landuse", 505 | "value": "residential", 506 | "description": "part of general builtup land" 507 | }, 508 | { 509 | "key": "landuse", 510 | "value": "orchard", 511 | "description": "plants on an agriculture land" 512 | }, 513 | { 514 | "key": "landuse", 515 | "value": "vineyard", 516 | "description": "plants on an agriculture land" 517 | }, 518 | { 519 | "key": "landuse", 520 | "value": "farmland", 521 | "description": "plants on an agriculture land" 522 | }, 523 | { 524 | "key": "landuse", 525 | "value": "village_green", 526 | "description": "recreation land" 527 | }, 528 | { 529 | "key": "leisure", 530 | "value": "playground", 531 | "description": "recreation land" 532 | }, 533 | { 534 | "key": "leisure", 535 | "value": "pitch", 536 | "description": "recreation land" 537 | }, 538 | { 539 | "key": "leisure", 540 | "value": "park", 541 | "description": "recreation land" 542 | }, 543 | { 544 | "key": "leisure", 545 | "value": "garden", 546 | "description": "vegetation that is not agriculture or forest" 547 | }, 548 | { 549 | "key": "natural", 550 | "value": "heath", 551 | "description": "vegetation that is not agriculture or forest" 552 | }, 553 | { 554 | "key": "natural", 555 | "value": "scrub", 556 | "description": "vegetation that is not agriculture or forest" 557 | }, 558 | { 559 | "key": "natural", 560 | "value": "grassland", 561 | "description": "vegetation that is not agriculture or forest" 562 | }, 563 | { 564 | "key": "landuse", 565 | "value": "meadow", 566 | "description": "vegetation that is not agriculture or forest" 567 | }, 568 | { 569 | "key": "landuse", 570 | "value": "allotments", 571 | "description": "vegetation that is not agriculture or forest" 572 | }, 573 | { 574 | "key": "landuse", 575 | "value": "grass", 576 | "description": "vegetation that is not agriculture or forest" 577 | }, 578 | { 579 | "key": "man_made", 580 | "value": "bridge", 581 | "description": "bridge outline" 582 | }, 583 | { 584 | "key": "natural", 585 | "value": "bare_rock", 586 | "description": "bare rock" 587 | }, 588 | { 589 | "key": "aeroway", 590 | "value": "runway", 591 | "description": "runway" 592 | }, 593 | { 594 | "key": "aeroway", 595 | "value": "taxiway", 596 | "description": "taxiway" 597 | } 598 | ] 599 | } 600 | -------------------------------------------------------------------------------- /examples/generated/laser_road_area_taginfo.json: -------------------------------------------------------------------------------- 1 | { 2 | "data_format": 1, 3 | "data_url": "https://raw.githubusercontent.com/matkoniecz/lunar_assembler/master/examples/generated/laser_road_area_taginfo.json", 4 | "project": { 5 | "name": "SVG for laser cutting a map based on area:highway", 6 | "description": "Map style to produce tactile maps for blind. Generated maps are for use in a laser cutter. Lunar Assembler map style.", 7 | "icon_url": "https://mapsaregreat.com/favicon.svg", 8 | "project_url": "https://github.com/matkoniecz/lunar_assembler", 9 | "doc_url": "https://mapsaregreat.com/osm_to_svg_in_browser/laser_road_area.html", 10 | "contact_name": "Mateusz Konieczny", 11 | "contact_email": "matkoniecz@tutanota.com" 12 | }, 13 | "tags": [ 14 | { 15 | "key": "area:highway", 16 | "value": "pedestrian", 17 | "description": "area of a pedestrian way (linear representation must be also present! Using only area representation is invalid!)" 18 | }, 19 | { 20 | "key": "area:highway", 21 | "value": "path", 22 | "description": "area of a pedestrian way (linear representation must be also present! Using only area representation is invalid!)" 23 | }, 24 | { 25 | "key": "area:highway", 26 | "value": "footway", 27 | "description": "area of a pedestrian way (linear representation must be also present! Using only area representation is invalid!)" 28 | }, 29 | { 30 | "key": "highway", 31 | "value": "pedestrian", 32 | "description": "pedestrian square (using it for sidewalk areas is invalid!)" 33 | }, 34 | { 35 | "key": "highway", 36 | "value": "pedestrian", 37 | "description": "pedestrian square (using it for sidewalk areas is invalid!)" 38 | }, 39 | { 40 | "key": "area:highway", 41 | "value": "crossing", 42 | "description": "pedestrian crossing through a road (area used in addition to area representing road)" 43 | }, 44 | { 45 | "key": "footway", 46 | "value": "crossing", 47 | "description": "detecting crossings" 48 | }, 49 | { 50 | "key": "area:highway", 51 | "value": "steps", 52 | "description": "area representation of steps (used in addition to linear highway=steps)" 53 | }, 54 | { 55 | "key": "area:highway", 56 | "value": "steps", 57 | "description": "area of steps, for an automatic generation of a symbolic representation" 58 | }, 59 | { 60 | "key": "highway", 61 | "value": "steps", 62 | "description": "detecting upper/lower side of steps, for an automatic generation of a symbolic representation" 63 | }, 64 | { 65 | "key": "incline", 66 | "value": "up", 67 | "description": "detecting upper/lower side of steps, for an automatic generation of a symbolic representation" 68 | }, 69 | { 70 | "key": "incline", 71 | "value": "down", 72 | "description": "detecting upper/lower side of steps, for an automatic generation of a symbolic representation" 73 | }, 74 | { 75 | "key": "area:highway", 76 | "value": "steps", 77 | "description": "area of steps, for an automatic generation of a symbolic representation" 78 | }, 79 | { 80 | "key": "highway", 81 | "value": "steps", 82 | "description": "detecting upper/lower side of steps, for an automatic generation of a symbolic representation" 83 | }, 84 | { 85 | "key": "incline", 86 | "value": "up", 87 | "description": "detecting upper/lower side of steps, for an automatic generation of a symbolic representation" 88 | }, 89 | { 90 | "key": "incline", 91 | "value": "down", 92 | "description": "detecting upper/lower side of steps, for an automatic generation of a symbolic representation" 93 | }, 94 | { 95 | "key": "area:highway", 96 | "value": "steps", 97 | "description": "area of steps, for an automatic generation of a symbolic representation" 98 | }, 99 | { 100 | "key": "highway", 101 | "value": "steps", 102 | "description": "detecting upper/lower side of steps, for an automatic generation of a symbolic representation" 103 | }, 104 | { 105 | "key": "incline", 106 | "value": "up", 107 | "description": "detecting upper/lower side of steps, for an automatic generation of a symbolic representation" 108 | }, 109 | { 110 | "key": "incline", 111 | "value": "down", 112 | "description": "detecting upper/lower side of steps, for an automatic generation of a symbolic representation" 113 | }, 114 | { 115 | "key": "area:highway", 116 | "value": "steps", 117 | "description": "area of steps, for an automatic generation of a symbolic representation" 118 | }, 119 | { 120 | "key": "highway", 121 | "value": "steps", 122 | "description": "detecting upper/lower side of steps, for an automatic generation of a symbolic representation" 123 | }, 124 | { 125 | "key": "incline", 126 | "value": "up", 127 | "description": "detecting upper/lower side of steps, for an automatic generation of a symbolic representation" 128 | }, 129 | { 130 | "key": "incline", 131 | "value": "down", 132 | "description": "detecting upper/lower side of steps, for an automatic generation of a symbolic representation" 133 | }, 134 | { 135 | "key": "building", 136 | "description": "buildings" 137 | }, 138 | { 139 | "key": "waterway", 140 | "value": "riverbank", 141 | "description": "water - pattern, part expected to be engraved" 142 | }, 143 | { 144 | "key": "natural", 145 | "value": "water", 146 | "description": "water - pattern, part expected to be engraved" 147 | }, 148 | { 149 | "key": "waterway", 150 | "value": "riverbank", 151 | "description": "water - entire area, expected to be cut at outline to separate element for easier painting (or used solely for orientation)" 152 | }, 153 | { 154 | "key": "natural", 155 | "value": "water", 156 | "description": "water - entire area, expected to be cut at outline to separate element for easier painting (or used solely for orientation)" 157 | }, 158 | { 159 | "key": "area:highway", 160 | "value": "living_street", 161 | "description": "area of a motorized road - pattern, part expected to be engraved" 162 | }, 163 | { 164 | "key": "area:highway", 165 | "value": "escape", 166 | "description": "area of a motorized road - pattern, part expected to be engraved" 167 | }, 168 | { 169 | "key": "area:highway", 170 | "value": "raceway", 171 | "description": "area of a motorized road - pattern, part expected to be engraved" 172 | }, 173 | { 174 | "key": "area:highway", 175 | "value": "busway", 176 | "description": "area of a motorized road - pattern, part expected to be engraved" 177 | }, 178 | { 179 | "key": "area:highway", 180 | "value": "road", 181 | "description": "area of a motorized road - pattern, part expected to be engraved" 182 | }, 183 | { 184 | "key": "area:highway", 185 | "value": "track", 186 | "description": "area of a motorized road - pattern, part expected to be engraved" 187 | }, 188 | { 189 | "key": "area:highway", 190 | "value": "service", 191 | "description": "area of a motorized road - pattern, part expected to be engraved" 192 | }, 193 | { 194 | "key": "area:highway", 195 | "value": "residential", 196 | "description": "area of a motorized road - pattern, part expected to be engraved" 197 | }, 198 | { 199 | "key": "area:highway", 200 | "value": "unclassified", 201 | "description": "area of a motorized road - pattern, part expected to be engraved" 202 | }, 203 | { 204 | "key": "area:highway", 205 | "value": "tertiary_link", 206 | "description": "area of a motorized road - pattern, part expected to be engraved" 207 | }, 208 | { 209 | "key": "area:highway", 210 | "value": "tertiary", 211 | "description": "area of a motorized road - pattern, part expected to be engraved" 212 | }, 213 | { 214 | "key": "area:highway", 215 | "value": "secondary_link", 216 | "description": "area of a motorized road - pattern, part expected to be engraved" 217 | }, 218 | { 219 | "key": "area:highway", 220 | "value": "secondary", 221 | "description": "area of a motorized road - pattern, part expected to be engraved" 222 | }, 223 | { 224 | "key": "area:highway", 225 | "value": "primary_link", 226 | "description": "area of a motorized road - pattern, part expected to be engraved" 227 | }, 228 | { 229 | "key": "area:highway", 230 | "value": "primary", 231 | "description": "area of a motorized road - pattern, part expected to be engraved" 232 | }, 233 | { 234 | "key": "area:highway", 235 | "value": "trunk_link", 236 | "description": "area of a motorized road - pattern, part expected to be engraved" 237 | }, 238 | { 239 | "key": "area:highway", 240 | "value": "trunk", 241 | "description": "area of a motorized road - pattern, part expected to be engraved" 242 | }, 243 | { 244 | "key": "area:highway", 245 | "value": "motorway_link", 246 | "description": "area of a motorized road - pattern, part expected to be engraved" 247 | }, 248 | { 249 | "key": "area:highway", 250 | "value": "motorway", 251 | "description": "area of a motorized road - pattern, part expected to be engraved" 252 | }, 253 | { 254 | "key": "area:highway", 255 | "value": "living_street", 256 | "description": "area of a motorized road (linear representation must be also present! Using only area representation is invalid!)" 257 | }, 258 | { 259 | "key": "area:highway", 260 | "value": "escape", 261 | "description": "area of a motorized road (linear representation must be also present! Using only area representation is invalid!)" 262 | }, 263 | { 264 | "key": "area:highway", 265 | "value": "raceway", 266 | "description": "area of a motorized road (linear representation must be also present! Using only area representation is invalid!)" 267 | }, 268 | { 269 | "key": "area:highway", 270 | "value": "busway", 271 | "description": "area of a motorized road (linear representation must be also present! Using only area representation is invalid!)" 272 | }, 273 | { 274 | "key": "area:highway", 275 | "value": "road", 276 | "description": "area of a motorized road (linear representation must be also present! Using only area representation is invalid!)" 277 | }, 278 | { 279 | "key": "area:highway", 280 | "value": "track", 281 | "description": "area of a motorized road (linear representation must be also present! Using only area representation is invalid!)" 282 | }, 283 | { 284 | "key": "area:highway", 285 | "value": "service", 286 | "description": "area of a motorized road (linear representation must be also present! Using only area representation is invalid!)" 287 | }, 288 | { 289 | "key": "area:highway", 290 | "value": "residential", 291 | "description": "area of a motorized road (linear representation must be also present! Using only area representation is invalid!)" 292 | }, 293 | { 294 | "key": "area:highway", 295 | "value": "unclassified", 296 | "description": "area of a motorized road (linear representation must be also present! Using only area representation is invalid!)" 297 | }, 298 | { 299 | "key": "area:highway", 300 | "value": "tertiary_link", 301 | "description": "area of a motorized road (linear representation must be also present! Using only area representation is invalid!)" 302 | }, 303 | { 304 | "key": "area:highway", 305 | "value": "tertiary", 306 | "description": "area of a motorized road (linear representation must be also present! Using only area representation is invalid!)" 307 | }, 308 | { 309 | "key": "area:highway", 310 | "value": "secondary_link", 311 | "description": "area of a motorized road (linear representation must be also present! Using only area representation is invalid!)" 312 | }, 313 | { 314 | "key": "area:highway", 315 | "value": "secondary", 316 | "description": "area of a motorized road (linear representation must be also present! Using only area representation is invalid!)" 317 | }, 318 | { 319 | "key": "area:highway", 320 | "value": "primary_link", 321 | "description": "area of a motorized road (linear representation must be also present! Using only area representation is invalid!)" 322 | }, 323 | { 324 | "key": "area:highway", 325 | "value": "primary", 326 | "description": "area of a motorized road (linear representation must be also present! Using only area representation is invalid!)" 327 | }, 328 | { 329 | "key": "area:highway", 330 | "value": "trunk_link", 331 | "description": "area of a motorized road (linear representation must be also present! Using only area representation is invalid!)" 332 | }, 333 | { 334 | "key": "area:highway", 335 | "value": "trunk", 336 | "description": "area of a motorized road (linear representation must be also present! Using only area representation is invalid!)" 337 | }, 338 | { 339 | "key": "area:highway", 340 | "value": "motorway_link", 341 | "description": "area of a motorized road (linear representation must be also present! Using only area representation is invalid!)" 342 | }, 343 | { 344 | "key": "area:highway", 345 | "value": "motorway", 346 | "description": "area of a motorized road (linear representation must be also present! Using only area representation is invalid!)" 347 | }, 348 | { 349 | "key": "area:highway", 350 | "value": "taxi_stop", 351 | "description": "road area of a taxi stop (used in addition to amenity=taxi) - pattern, part expected to be engraved" 352 | }, 353 | { 354 | "key": "area:highway", 355 | "value": "taxi_stop", 356 | "description": "road area of a taxi stop (used in addition to amenity=taxi)" 357 | }, 358 | { 359 | "key": "area:highway", 360 | "value": "bus_stop", 361 | "description": "road area of a bus stop - pattern, part expected to be engraved" 362 | }, 363 | { 364 | "key": "area:highway", 365 | "value": "bus_stop", 366 | "description": "road area of a bus stop (used in addition to highway=bus_stop)" 367 | }, 368 | { 369 | "key": "area:highway", 370 | "value": "cycleway", 371 | "description": "road area of a cycleway - pattern, part expected to be engraved" 372 | }, 373 | { 374 | "key": "area:highway", 375 | "value": "cycleway", 376 | "description": "road area of a cycleway (used in addition to highway=bus_stop)" 377 | }, 378 | { 379 | "key": "barrier", 380 | "value": "haha", 381 | "description": "generally impassable barrier, for detecting where access is blocked" 382 | }, 383 | { 384 | "key": "barrier", 385 | "value": "guard_rail", 386 | "description": "generally impassable barrier, for detecting where access is blocked" 387 | }, 388 | { 389 | "key": "barrier", 390 | "value": "city_wall", 391 | "description": "generally impassable barrier, for detecting where access is blocked" 392 | }, 393 | { 394 | "key": "barrier", 395 | "value": "wire_fence", 396 | "description": "generally impassable barrier, for detecting where access is blocked" 397 | }, 398 | { 399 | "key": "barrier", 400 | "value": "hedge_bank", 401 | "description": "generally impassable barrier, for detecting where access is blocked" 402 | }, 403 | { 404 | "key": "barrier", 405 | "value": "retaining_wall", 406 | "description": "generally impassable barrier, for detecting where access is blocked" 407 | }, 408 | { 409 | "key": "barrier", 410 | "value": "hedge", 411 | "description": "generally impassable barrier, for detecting where access is blocked" 412 | }, 413 | { 414 | "key": "barrier", 415 | "value": "wall", 416 | "description": "generally impassable barrier, for detecting where access is blocked" 417 | }, 418 | { 419 | "key": "barrier", 420 | "value": "fence", 421 | "description": "generally impassable barrier, for detecting where access is blocked" 422 | }, 423 | { 424 | "key": "barrier", 425 | "value": "yes", 426 | "description": "unknown barrier, assumed to be generally impassable barrier, for detecting where access is blocked" 427 | }, 428 | { 429 | "key": "barrier", 430 | "value": "haha", 431 | "description": "generally impassable barrier, for detecting where access is blocked" 432 | }, 433 | { 434 | "key": "barrier", 435 | "value": "guard_rail", 436 | "description": "generally impassable barrier, for detecting where access is blocked" 437 | }, 438 | { 439 | "key": "barrier", 440 | "value": "city_wall", 441 | "description": "generally impassable barrier, for detecting where access is blocked" 442 | }, 443 | { 444 | "key": "barrier", 445 | "value": "wire_fence", 446 | "description": "generally impassable barrier, for detecting where access is blocked" 447 | }, 448 | { 449 | "key": "barrier", 450 | "value": "hedge_bank", 451 | "description": "generally impassable barrier, for detecting where access is blocked" 452 | }, 453 | { 454 | "key": "barrier", 455 | "value": "retaining_wall", 456 | "description": "generally impassable barrier, for detecting where access is blocked" 457 | }, 458 | { 459 | "key": "barrier", 460 | "value": "hedge", 461 | "description": "generally impassable barrier, for detecting where access is blocked" 462 | }, 463 | { 464 | "key": "barrier", 465 | "value": "wall", 466 | "description": "generally impassable barrier, for detecting where access is blocked" 467 | }, 468 | { 469 | "key": "barrier", 470 | "value": "fence", 471 | "description": "generally impassable barrier, for detecting where access is blocked" 472 | }, 473 | { 474 | "key": "barrier", 475 | "value": "yes", 476 | "description": "unknown barrier, assumed to be generally impassable barrier, for detecting where access is blocked" 477 | }, 478 | { 479 | "key": "natural", 480 | "value": "water", 481 | "description": "generally impassable barrier, for detecting where access is blocked" 482 | }, 483 | { 484 | "key": "waterway", 485 | "value": "riverbank", 486 | "description": "generally impassable barrier, for detecting where access is blocked" 487 | }, 488 | { 489 | "key": "building", 490 | "description": "generally impassable barrier, for detecting where access is blocked" 491 | }, 492 | { 493 | "key": "waterway", 494 | "value": "riverbank", 495 | "description": "generally impassable barrier, for detecting where access is blocked" 496 | }, 497 | { 498 | "key": "area:highway", 499 | "description": "generally impassable barrier, for detecting where access is blocked" 500 | }, 501 | { 502 | "key": "foot", 503 | "value": "no", 504 | "description": "generally impassable barrier, for detecting where access is blocked" 505 | }, 506 | { 507 | "key": "railway", 508 | "value": "monorail", 509 | "description": "linear representation of a single railway track" 510 | }, 511 | { 512 | "key": "railway", 513 | "value": "miniature", 514 | "description": "linear representation of a single railway track" 515 | }, 516 | { 517 | "key": "railway", 518 | "value": "construction", 519 | "description": "linear representation of a single railway track" 520 | }, 521 | { 522 | "key": "railway", 523 | "value": "preserved", 524 | "description": "linear representation of a single railway track" 525 | }, 526 | { 527 | "key": "railway", 528 | "value": "light_rail", 529 | "description": "linear representation of a single railway track" 530 | }, 531 | { 532 | "key": "railway", 533 | "value": "narrow_gauge", 534 | "description": "linear representation of a single railway track" 535 | }, 536 | { 537 | "key": "railway", 538 | "value": "subway", 539 | "description": "linear representation of a single railway track" 540 | }, 541 | { 542 | "key": "railway", 543 | "value": "tram", 544 | "description": "linear representation of a single railway track" 545 | }, 546 | { 547 | "key": "railway", 548 | "value": "disused", 549 | "description": "linear representation of a single railway track" 550 | }, 551 | { 552 | "key": "railway", 553 | "value": "rail", 554 | "description": "linear representation of a single railway track" 555 | } 556 | ] 557 | } 558 | -------------------------------------------------------------------------------- /examples/laser_neighbourhood.html: -------------------------------------------------------------------------------- 1 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | Laser neighbourhood - SVG map from OpenStreetMap data 25 | 29 | 30 | 31 | 32 |
33 |
34 |

Generation of vector maps from OpenStreetMap data, for use in laser cutters for producing tactile maps.

35 | 36 |

Press the rectangle button, select a rectangle on a map and wait. Note that this tool will work well with small areas, such as a neighbourhood.

37 | 38 |

See also other map styles, allowing generation of other maps from the same OpenStreetMap data.

39 | 40 |

(there is more text below map, you can also see an animation showing how this tool is supposed to work)

41 | 42 |
43 |
44 | 45 |
46 |
47 | 48 |

49 |

56 |

57 | 58 |

Map style is generating SVG design files for use in a laser cutter. As orientation maps for blind people of a very small area, such as a single road crossing.

59 |

laser parameters that I used are given, but this is a solely example! You will need to test it on laser cutter you use which parameters are optimal. Yes, it is time consuming

60 |

TODO: finish describing parameters at closeup, clone relevant ones

61 | 62 |

Examples

63 | 64 |
65 | please send an email to matkoniecz@tutanota.com if replacing this placeholder alt attribute of image by a real alt attribute would be useful for you 66 |

Examples of a map generated from OpenStreetMap data, on ODbL license.

67 |
68 | 69 |

Even more info

70 |

Have fun with using OpenStreetMap data for interesting and/or useful purposes! Just remember that you must mention the source of data in a way visible to whoever will be using it. See this page for details, including cases where it is legally allowed to avoid giving a clear credit (but I encourage to do this in all cases).

71 | 72 |

If you liked this tool, something was confusing - you are welcomed to comment about it by creating a public issue or by sending me an email. You can also post a comment in OSM diary entry.

73 | 74 |

If you want to help mapping and you are unsure how to start - you can try StreetComplete (an Android app) allowing contributions, with sole requirements being (1) ability to read (2) ability to see things (3) ability to create an OpenStreetMap account.

75 | 76 |

If you want to map something (map missing objects, fix a mistake) and you are unsure how to start - visit openstreetmap.org, zoom in to interesting area and press the "edit" button and follow instructions. You can contact OpenStreetMap community using one of global or local channels listed at community.osm.be.

77 | 78 |

You can visit matkoniecz/lunar_assembler to see code, obtain code or contribute. This software is AGPLv3 licenced.

79 | 80 | 81 |

Legend

82 |

83 | 84 |
85 | 86 | 87 | 88 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /examples/laser_neighbourhood_-_Madalińskiego.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matkoniecz/lunar_assembler/45fb1e623f78b23e13fc8e60f660b51ef6736f59/examples/laser_neighbourhood_-_Madalińskiego.png -------------------------------------------------------------------------------- /examples/laser_road_area.html: -------------------------------------------------------------------------------- 1 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | Laser road area - SVG map from OpenStreetMap data 25 | 29 | 30 | 31 | 32 |
33 |
34 |

Generation of vector maps from OpenStreetMap data, for use in laser cutters for producing tactile maps.

35 | 36 |

Press the rectangle button, select a rectangle on a map and wait. Note that this tool will work well with tiny areas, such as a single crossing.

37 | 38 |

This map style requires an area to be mapped with area:highway, otherwise it will not work.

39 | 40 |

See also other map styles, allowing generation of other maps from the same OpenStreetMap data. One of available generates laser cut designs for 3D maps, without need of area:highway mapping.

41 | 42 |

(there is more text below the map, you can also see an animation showing how this tool is supposed to work)

43 | 44 |
45 |
46 | 47 |
48 |
49 | 50 |
51 | please send an email to matkoniecz@tutanota.com if replacing this placeholder alt attribute of image by a real alt attribute would be useful for you 52 |

OpenStreetMap data (ODbL licensed) and this map style combined to make a 3D tactile map for the blind.

53 |
54 |

55 |

62 |

63 |

Map style is generating SVG design files for use in a laser cutter. Intended for making orientation maps for blind people of a very small area, such as a single road crossing.

64 | 65 |

Have fun with using OpenStreetMap data for interesting and/or useful purposes! Just remember that you must mention the source of data in a way visible to whoever will be using it. See this page for details, including cases where it is legally allowed to avoid giving a clear credit (but I encourage to do this in all cases).

66 | 67 |

If you liked this tool, something was confusing - you are welcomed to comment about it by creating a public issue or by sending me an email. You can also post a comment in OSM diary entry.

68 | 69 |

If you want to help mapping and you are unsure how to start - you can try StreetComplete (an Android app) allowing contributions, with sole requirements being (1) ability to read (2) ability to see things (3) ability to create an OpenStreetMap account.

70 | 71 |

If you want to map something (map missing objects, fix a mistake) and you are unsure how to start - visit openstreetmap.org, zoom in to an interesting area and press the "edit" button and follow instructions. You can contact the OpenStreetMap community using one of global or local channels listed at community.osm.be and ask for help.

72 | 73 | You can visit matkoniecz/lunar_assembler to see code, obtain code or contribute. This software is AGPLv3 licenced. 74 | 75 |

Legend

76 |

77 |

Laser parameters

78 |

laser parameters that I used are given below, but this is a solely example! You will need to test it on laser cutter you use which parameters are optimal.

79 |

80 | Produced SVG is intended to be copied and used for multiple cuts: 81 |

    82 |
  1. 83 | Empty base area, to keep elements together 84 |
  2. 85 |
  3. 86 | Base layer: 87 |
      88 |
    • engraving road areas surface with own pattern - 400 speed 40 power 0.08mm line interval on laser that I used
    • 89 |
    • cutting road areas from other areas (necessary to paint it properly) - ???
    • 90 |
    • everything else - quick delicate burn to indicate where given elements should be glued - ?????
    • 91 |
    92 |
  4. 93 |
  5. separate cuts of footway areas and crossing - I cut them into plexiglass to achieve clearly different surface in touch. I considered using leather or cork. For transparent plexiglass it is possible to spray-paint its bottom to change it colour to a desired one
  6. 94 |
  7. separate cut for buildings, I used thick plywood to make it clearly distinct
  8. 95 |
  9. separate cut for steps from plywood. This is a a quite tricky layer: first of all area:highway is processed and split into symbolic areas 96 |
      97 |
    • outer shape surrounding entire steps area: should cut through to produce a separate piece
    • 98 |
    • symbolic steps - should be engraved as areas, each with increasing power to burn wood to a different depth. 99 |
        100 |
      1. 20% power, 1 repetition, line interval 0.08mm
      2. 101 |
      3. MAX power (80% on this specific laser), 1 repetitions, line interval 0.08mm
      4. 102 |
      5. MAX power, 2 repetitions, line interval 0.08mm
      6. 103 |
      7. MAX power, 3 repetitions, line interval 0.08mm
      8. 104 |
      105 |
    • 106 |
    107 |
  10. 108 |
109 |

110 |
111 | 112 | 113 | 114 | 119 | 120 | 121 | -------------------------------------------------------------------------------- /examples/laser_road_area_prototype_delivered_cropped.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matkoniecz/lunar_assembler/45fb1e623f78b23e13fc8e60f660b51ef6736f59/examples/laser_road_area_prototype_delivered_cropped.jpg -------------------------------------------------------------------------------- /examples/lunar_assembler_in_action.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matkoniecz/lunar_assembler/45fb1e623f78b23e13fc8e60f660b51ef6736f59/examples/lunar_assembler_in_action.gif -------------------------------------------------------------------------------- /examples/simple.html: -------------------------------------------------------------------------------- 1 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | Simple map style - SVG map from OpenStreetMap data 25 | 29 | 30 | 31 | 32 |
33 |
34 |

Press the rectangle button, select a rectangle on a map and wait. It will generate map from OpenStreetMap data It is an example of a very simple map style, showing water, forest, buildings and marinas.

35 | 36 |
37 |
38 | 39 |
40 |
41 | 42 | 43 |

More detailed instructions

44 |

45 |

52 |

53 | 54 |

Even more info

55 |

Have fun with using OpenStreetMap data for interesting and/or useful purposes! Just remember that you must mention the source of data in a way visible to whoever will be using it. See this page for details, including cases where it is legally allowed to avoid giving a clear credit (but I encourage to do this in all cases).

56 | 57 |

If you liked this tool, something was confusing - you are welcomed to comment about it by creating a public issue or by sending me an email. You can also post a comment in OSM diary entry.

58 | 59 |

If you want to map something (map missing objects, fix a mistake) and you are unsure how to start - visit openstreetmap.org, zoom in to an interesting area and press the "edit" button and follow instructions. You can contact the OpenStreetMap community using one of global or local channels listed at community.osm.be and ask for help.

60 | 61 |

You can visit matkoniecz/lunar_assembler to see code, obtain code or contribute. This software is AGPLv3 licenced.

62 | 63 |

Legend

64 | 65 |

66 | 67 | 68 |
69 | 70 | 71 | 156 | 157 | 158 | -------------------------------------------------------------------------------- /examples/simple_map_style_-_Sztynort.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matkoniecz/lunar_assembler/45fb1e623f78b23e13fc8e60f660b51ef6736f59/examples/simple_map_style_-_Sztynort.png -------------------------------------------------------------------------------- /examples/taginfo_file_generate.js: -------------------------------------------------------------------------------- 1 | var fs = require("fs"); 2 | 3 | // yes it is hack - if you are aware about a better way let me know 4 | // note that files getting imported must continue working in browser 5 | // change must not require massive restructuring of existing code 6 | eval(fs.readFileSync("../lunar_assembler_helpful_functions_for_map_styles_openstreetmap_tagging_knowledge.js") + ""); 7 | eval(fs.readFileSync("../lunar_assembler_helpful_functions_for_map_styles_unified_styling_handler.js") + ""); 8 | eval(fs.readFileSync("../lunar_assembler_helpful_functions_for_map_styles_generate_symbolic_steps_from_area_highway.js") + ""); 9 | eval(fs.readFileSync("../lunar_assembler_helpful_functions_for_map_styles_generate_inaccessible_areas.js") + ""); 10 | eval(fs.readFileSync("general_high_zoom_map_style.js") + ""); 11 | eval(fs.readFileSync("laser_road_area_map_style.js") + ""); 12 | eval(fs.readFileSync("laser_neighbourhood_map_style.js") + ""); 13 | 14 | // syntax_documentation: https://wiki.openstreetmap.org/wiki/Taginfo/Projects#Project_Files 15 | function infoDocsJSON(dataURL, projectName, projectDescription, projectDocs, tagList) { 16 | return { 17 | data_format: 1, 18 | data_url: dataURL, 19 | project: { 20 | name: projectName, 21 | description: projectDescription, 22 | icon_url: "https://mapsaregreat.com/favicon.svg", 23 | project_url: "https://github.com/matkoniecz/lunar_assembler", 24 | doc_url: projectDocs, 25 | contact_name: "Mateusz Konieczny", 26 | contact_email: "matkoniecz@tutanota.com", 27 | }, 28 | tags: tagList, 29 | }; 30 | } 31 | 32 | function showErrorIfpresent(error) { 33 | if (error != null) { 34 | console.error(error); 35 | } 36 | } 37 | 38 | function geenratePrettyJsonString(json) { 39 | return JSON.stringify(json, null, 2) + "\n"; 40 | } 41 | function writeGeneratedTaginfoSummary(taginfoData, filename) { 42 | fs.writeFile(filename, geenratePrettyJsonString(taginfoData), showErrorIfpresent); 43 | } 44 | var taginfo, filename; 45 | 46 | filename = "generated/general_high_zoom_taginfo.json"; 47 | taginfo = infoDocsJSON( 48 | "https://raw.githubusercontent.com/matkoniecz/lunar_assembler/master/examples/" + filename, 49 | "general SVG maps with area:highway, for zoomed in areas (city and smaller)", 50 | "Map style to produce SVG maps of tiny and small areas - from single crossing to a city. Lunar Assembler map style.", 51 | "https://mapsaregreat.com/osm_to_svg_in_browser/general_high_zoom", 52 | generateTaginfoListing(highZoomMapStyle().unifiedStyling()) 53 | ); 54 | writeGeneratedTaginfoSummary(taginfo, filename); 55 | 56 | filename = "generated/laser_road_area_taginfo.json"; 57 | taginfo = infoDocsJSON( 58 | "https://raw.githubusercontent.com/matkoniecz/lunar_assembler/master/examples/" + filename, 59 | "SVG for laser cutting a map based on area:highway", 60 | "Map style to produce tactile maps for blind. Generated maps are for use in a laser cutter. Lunar Assembler map style.", 61 | "https://mapsaregreat.com/osm_to_svg_in_browser/laser_road_area.html", 62 | generateTaginfoListing(laserRoadAreaMapStyle().unifiedStyling()) 63 | ); 64 | writeGeneratedTaginfoSummary(taginfo, filename); 65 | 66 | filename = "generated/laser_neighbourhood_taginfo.json"; 67 | taginfo = infoDocsJSON( 68 | "https://raw.githubusercontent.com/matkoniecz/lunar_assembler/master/examples/" + filename, 69 | "SVG for laser cutting a map of neighbourhood", 70 | "Map style to produce tactile maps for blind. Generated maps are for use in a laser cutter. Lunar Assembler map style.", 71 | "https://mapsaregreat.com/osm_to_svg_in_browser/laser_neighbourhood.html", 72 | generateTaginfoListing(highZoomLaserMapStyle().unifiedStyling()) 73 | ); 74 | writeGeneratedTaginfoSummary(taginfo, filename); 75 | -------------------------------------------------------------------------------- /images_for_description/logo_osmf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matkoniecz/lunar_assembler/45fb1e623f78b23e13fc8e60f660b51ef6736f59/images_for_description/logo_osmf.png -------------------------------------------------------------------------------- /lunar_assembler_dependencies/COPYING: -------------------------------------------------------------------------------- 1 | dependencies have their own licenses -------------------------------------------------------------------------------- /lunar_assembler_dependencies/leaflet-draw.v-zombie-bleeding-edge-latest-commit-with-manually-inlined-rectangle-svg.css: -------------------------------------------------------------------------------- 1 | /* ================================================================== */ 2 | /* Toolbars 3 | /* ================================================================== */ 4 | 5 | .leaflet-draw-section { 6 | position: relative; 7 | } 8 | 9 | .leaflet-draw-toolbar { 10 | margin-top: 12px; 11 | } 12 | 13 | .leaflet-draw-toolbar-top { 14 | margin-top: 0; 15 | } 16 | 17 | .leaflet-draw-toolbar-notop a:first-child { 18 | border-top-right-radius: 0; 19 | } 20 | 21 | .leaflet-draw-toolbar-nobottom a:last-child { 22 | border-bottom-right-radius: 0; 23 | } 24 | 25 | .leaflet-draw-toolbar a { 26 | /* 27 | background-image: url('images/spritesheet.png'); 28 | background-image: linear-gradient(transparent, transparent), url('images/spritesheet.svg'); 29 | */ 30 | /* 31 | inlined SVG: 32 | 33 | 34 | 53 | 55 | 56 | 58 | image/svg+xml 59 | 61 | 62 | 63 | 64 | 65 | 67 | 87 | 90 | 95 | 96 | 100 | 107 | 114 | 115 | 116 | 117 | */ 118 | background-image: url("data:image/svg+xml;utf8,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22UTF-8%22%20standalone%3D%22no%22%3F%3E%0A%3Csvg%0A%20%20%20xmlns%3Adc%3D%22http%3A%2F%2Fpurl.org%2Fdc%2Felements%2F1.1%2F%22%0A%20%20%20xmlns%3Acc%3D%22http%3A%2F%2Fcreativecommons.org%2Fns%23%22%0A%20%20%20xmlns%3Ardf%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2F02%2F22-rdf-syntax-ns%23%22%0A%20%20%20xmlns%3Asvg%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%0A%20%20%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%0A%20%20%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%0A%20%20%20xmlns%3Asodipodi%3D%22http%3A%2F%2Fsodipodi.sourceforge.net%2FDTD%2Fsodipodi-0.dtd%22%0A%20%20%20xmlns%3Ainkscape%3D%22http%3A%2F%2Fwww.inkscape.org%2Fnamespaces%2Finkscape%22%0A%20%20%20viewBox%3D%220%200%20600%2060%22%0A%20%20%20height%3D%2260%22%0A%20%20%20width%3D%22600%22%0A%20%20%20id%3D%22svg4225%22%0A%20%20%20version%3D%221.1%22%0A%20%20%20inkscape%3Aversion%3D%220.91%20r13725%22%0A%20%20%20sodipodi%3Adocname%3D%22spritesheet.svg%22%0A%20%20%20inkscape%3Aexport-filename%3D%22%2Fhome%2Ffpuga%2Fdevelopment%2Fupstream%2Ficarto.Leaflet.draw%2Fsrc%2Fimages%2Fspritesheet-2x.png%22%0A%20%20%20inkscape%3Aexport-xdpi%3D%2290%22%0A%20%20%20inkscape%3Aexport-ydpi%3D%2290%22%3E%0A%20%20%3Cmetadata%0A%20%20%20%20%20id%3D%22metadata4258%22%3E%0A%20%20%20%20%3Crdf%3ARDF%3E%0A%20%20%20%20%20%20%3Ccc%3AWork%0A%20%20%20%20%20%20%20%20%20rdf%3Aabout%3D%22%22%3E%0A%20%20%20%20%20%20%20%20%3Cdc%3Aformat%3Eimage%2Fsvg%2Bxml%3C%2Fdc%3Aformat%3E%0A%20%20%20%20%20%20%20%20%3Cdc%3Atype%0A%20%20%20%20%20%20%20%20%20%20%20rdf%3Aresource%3D%22http%3A%2F%2Fpurl.org%2Fdc%2Fdcmitype%2FStillImage%22%20%2F%3E%0A%20%20%20%20%20%20%20%20%3Cdc%3Atitle%20%2F%3E%0A%20%20%20%20%20%20%3C%2Fcc%3AWork%3E%0A%20%20%20%20%3C%2Frdf%3ARDF%3E%0A%20%20%3C%2Fmetadata%3E%0A%20%20%3Cdefs%0A%20%20%20%20%20id%3D%22defs4256%22%20%2F%3E%0A%20%20%3Csodipodi%3Anamedview%0A%20%20%20%20%20pagecolor%3D%22%23ffffff%22%0A%20%20%20%20%20bordercolor%3D%22%23666666%22%0A%20%20%20%20%20borderopacity%3D%221%22%0A%20%20%20%20%20objecttolerance%3D%2210%22%0A%20%20%20%20%20gridtolerance%3D%2210%22%0A%20%20%20%20%20guidetolerance%3D%2210%22%0A%20%20%20%20%20inkscape%3Apageopacity%3D%220%22%0A%20%20%20%20%20inkscape%3Apageshadow%3D%222%22%0A%20%20%20%20%20inkscape%3Awindow-width%3D%221920%22%0A%20%20%20%20%20inkscape%3Awindow-height%3D%221056%22%0A%20%20%20%20%20id%3D%22namedview4254%22%0A%20%20%20%20%20showgrid%3D%22false%22%0A%20%20%20%20%20inkscape%3Azoom%3D%221.3101852%22%0A%20%20%20%20%20inkscape%3Acx%3D%22237.56928%22%0A%20%20%20%20%20inkscape%3Acy%3D%227.2419621%22%0A%20%20%20%20%20inkscape%3Awindow-x%3D%221920%22%0A%20%20%20%20%20inkscape%3Awindow-y%3D%2224%22%0A%20%20%20%20%20inkscape%3Awindow-maximized%3D%221%22%0A%20%20%20%20%20inkscape%3Acurrent-layer%3D%22svg4225%22%20%2F%3E%0A%20%20%3Cg%0A%20%20%20%20%20id%3D%22enabled%22%0A%20%20%20%20%20style%3D%22fill%3A%23464646%3Bfill-opacity%3A1%22%3E%0A%20%20%20%20%3Cpath%0A%20%20%20%20%20%20%20id%3D%22rectangle%22%0A%20%20%20%20%20%20%20d%3D%22m%20140%2C20%2020%2C0%200%2C20%20-20%2C0%20z%22%0A%20%20%20%20%20%20%20inkscape%3Aconnector-curvature%3D%220%22%0A%20%20%20%20%20%20%20style%3D%22fill%3A%23464646%3Bfill-opacity%3A1%22%20%2F%3E%0A%20%20%3C%2Fg%3E%0A%20%20%3Cg%0A%20%20%20%20%20id%3D%22disabled%22%0A%20%20%20%20%20transform%3D%22translate(120%2C0)%22%0A%20%20%20%20%20style%3D%22fill%3A%23bbbbbb%22%3E%0A%20%20%20%20%3Cuse%0A%20%20%20%20%20%20%20xlink%3Ahref%3D%22%23edit%22%0A%20%20%20%20%20%20%20id%3D%22edit-disabled%22%0A%20%20%20%20%20%20%20x%3D%220%22%0A%20%20%20%20%20%20%20y%3D%220%22%0A%20%20%20%20%20%20%20width%3D%22100%25%22%0A%20%20%20%20%20%20%20height%3D%22100%25%22%20%2F%3E%0A%20%20%20%20%3Cuse%0A%20%20%20%20%20%20%20xlink%3Ahref%3D%22%23remove%22%0A%20%20%20%20%20%20%20id%3D%22remove-disabled%22%0A%20%20%20%20%20%20%20x%3D%220%22%0A%20%20%20%20%20%20%20y%3D%220%22%0A%20%20%20%20%20%20%20width%3D%22100%25%22%0A%20%20%20%20%20%20%20height%3D%22100%25%22%20%2F%3E%0A%20%20%3C%2Fg%3E%0A%3C%2Fsvg%3E%0A"); 119 | background-repeat: no-repeat; 120 | background-size: 300px 30px; 121 | background-clip: padding-box; 122 | } 123 | 124 | .leaflet-retina .leaflet-draw-toolbar a { 125 | /* 126 | background-image: url('images/spritesheet-2x.png'); 127 | background-image: linear-gradient(transparent, transparent), url('images/spritesheet.svg'); 128 | */ 129 | /* 130 | for content of inlined SVG see above */ 131 | background-image: url("data:image/svg+xml;utf8,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22UTF-8%22%20standalone%3D%22no%22%3F%3E%0A%3Csvg%0A%20%20%20xmlns%3Adc%3D%22http%3A%2F%2Fpurl.org%2Fdc%2Felements%2F1.1%2F%22%0A%20%20%20xmlns%3Acc%3D%22http%3A%2F%2Fcreativecommons.org%2Fns%23%22%0A%20%20%20xmlns%3Ardf%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2F02%2F22-rdf-syntax-ns%23%22%0A%20%20%20xmlns%3Asvg%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%0A%20%20%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%0A%20%20%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%0A%20%20%20xmlns%3Asodipodi%3D%22http%3A%2F%2Fsodipodi.sourceforge.net%2FDTD%2Fsodipodi-0.dtd%22%0A%20%20%20xmlns%3Ainkscape%3D%22http%3A%2F%2Fwww.inkscape.org%2Fnamespaces%2Finkscape%22%0A%20%20%20viewBox%3D%220%200%20600%2060%22%0A%20%20%20height%3D%2260%22%0A%20%20%20width%3D%22600%22%0A%20%20%20id%3D%22svg4225%22%0A%20%20%20version%3D%221.1%22%0A%20%20%20inkscape%3Aversion%3D%220.91%20r13725%22%0A%20%20%20sodipodi%3Adocname%3D%22spritesheet.svg%22%0A%20%20%20inkscape%3Aexport-filename%3D%22%2Fhome%2Ffpuga%2Fdevelopment%2Fupstream%2Ficarto.Leaflet.draw%2Fsrc%2Fimages%2Fspritesheet-2x.png%22%0A%20%20%20inkscape%3Aexport-xdpi%3D%2290%22%0A%20%20%20inkscape%3Aexport-ydpi%3D%2290%22%3E%0A%20%20%3Cmetadata%0A%20%20%20%20%20id%3D%22metadata4258%22%3E%0A%20%20%20%20%3Crdf%3ARDF%3E%0A%20%20%20%20%20%20%3Ccc%3AWork%0A%20%20%20%20%20%20%20%20%20rdf%3Aabout%3D%22%22%3E%0A%20%20%20%20%20%20%20%20%3Cdc%3Aformat%3Eimage%2Fsvg%2Bxml%3C%2Fdc%3Aformat%3E%0A%20%20%20%20%20%20%20%20%3Cdc%3Atype%0A%20%20%20%20%20%20%20%20%20%20%20rdf%3Aresource%3D%22http%3A%2F%2Fpurl.org%2Fdc%2Fdcmitype%2FStillImage%22%20%2F%3E%0A%20%20%20%20%20%20%20%20%3Cdc%3Atitle%20%2F%3E%0A%20%20%20%20%20%20%3C%2Fcc%3AWork%3E%0A%20%20%20%20%3C%2Frdf%3ARDF%3E%0A%20%20%3C%2Fmetadata%3E%0A%20%20%3Cdefs%0A%20%20%20%20%20id%3D%22defs4256%22%20%2F%3E%0A%20%20%3Csodipodi%3Anamedview%0A%20%20%20%20%20pagecolor%3D%22%23ffffff%22%0A%20%20%20%20%20bordercolor%3D%22%23666666%22%0A%20%20%20%20%20borderopacity%3D%221%22%0A%20%20%20%20%20objecttolerance%3D%2210%22%0A%20%20%20%20%20gridtolerance%3D%2210%22%0A%20%20%20%20%20guidetolerance%3D%2210%22%0A%20%20%20%20%20inkscape%3Apageopacity%3D%220%22%0A%20%20%20%20%20inkscape%3Apageshadow%3D%222%22%0A%20%20%20%20%20inkscape%3Awindow-width%3D%221920%22%0A%20%20%20%20%20inkscape%3Awindow-height%3D%221056%22%0A%20%20%20%20%20id%3D%22namedview4254%22%0A%20%20%20%20%20showgrid%3D%22false%22%0A%20%20%20%20%20inkscape%3Azoom%3D%221.3101852%22%0A%20%20%20%20%20inkscape%3Acx%3D%22237.56928%22%0A%20%20%20%20%20inkscape%3Acy%3D%227.2419621%22%0A%20%20%20%20%20inkscape%3Awindow-x%3D%221920%22%0A%20%20%20%20%20inkscape%3Awindow-y%3D%2224%22%0A%20%20%20%20%20inkscape%3Awindow-maximized%3D%221%22%0A%20%20%20%20%20inkscape%3Acurrent-layer%3D%22svg4225%22%20%2F%3E%0A%20%20%3Cg%0A%20%20%20%20%20id%3D%22enabled%22%0A%20%20%20%20%20style%3D%22fill%3A%23464646%3Bfill-opacity%3A1%22%3E%0A%20%20%20%20%3Cpath%0A%20%20%20%20%20%20%20id%3D%22rectangle%22%0A%20%20%20%20%20%20%20d%3D%22m%20140%2C20%2020%2C0%200%2C20%20-20%2C0%20z%22%0A%20%20%20%20%20%20%20inkscape%3Aconnector-curvature%3D%220%22%0A%20%20%20%20%20%20%20style%3D%22fill%3A%23464646%3Bfill-opacity%3A1%22%20%2F%3E%0A%20%20%3C%2Fg%3E%0A%20%20%3Cg%0A%20%20%20%20%20id%3D%22disabled%22%0A%20%20%20%20%20transform%3D%22translate(120%2C0)%22%0A%20%20%20%20%20style%3D%22fill%3A%23bbbbbb%22%3E%0A%20%20%20%20%3Cuse%0A%20%20%20%20%20%20%20xlink%3Ahref%3D%22%23edit%22%0A%20%20%20%20%20%20%20id%3D%22edit-disabled%22%0A%20%20%20%20%20%20%20x%3D%220%22%0A%20%20%20%20%20%20%20y%3D%220%22%0A%20%20%20%20%20%20%20width%3D%22100%25%22%0A%20%20%20%20%20%20%20height%3D%22100%25%22%20%2F%3E%0A%20%20%20%20%3Cuse%0A%20%20%20%20%20%20%20xlink%3Ahref%3D%22%23remove%22%0A%20%20%20%20%20%20%20id%3D%22remove-disabled%22%0A%20%20%20%20%20%20%20x%3D%220%22%0A%20%20%20%20%20%20%20y%3D%220%22%0A%20%20%20%20%20%20%20width%3D%22100%25%22%0A%20%20%20%20%20%20%20height%3D%22100%25%22%20%2F%3E%0A%20%20%3C%2Fg%3E%0A%3C%2Fsvg%3E%0A"); 132 | } 133 | 134 | .leaflet-draw a { 135 | display: block; 136 | text-align: center; 137 | text-decoration: none; 138 | } 139 | 140 | .leaflet-draw a .sr-only { 141 | position: absolute; 142 | width: 1px; 143 | height: 1px; 144 | padding: 0; 145 | margin: -1px; 146 | overflow: hidden; 147 | clip: rect(0, 0, 0, 0); 148 | border: 0; 149 | } 150 | 151 | /* ================================================================== */ 152 | /* Toolbar actions menu 153 | /* ================================================================== */ 154 | 155 | .leaflet-draw-actions { 156 | display: none; 157 | list-style: none; 158 | margin: 0; 159 | padding: 0; 160 | position: absolute; 161 | left: 26px; /* leaflet-draw-toolbar.left + leaflet-draw-toolbar.width */ 162 | top: 0; 163 | white-space: nowrap; 164 | } 165 | 166 | .leaflet-touch .leaflet-draw-actions { 167 | left: 32px; 168 | } 169 | 170 | .leaflet-right .leaflet-draw-actions { 171 | right: 26px; 172 | left: auto; 173 | } 174 | 175 | .leaflet-touch .leaflet-right .leaflet-draw-actions { 176 | right: 32px; 177 | left: auto; 178 | } 179 | 180 | .leaflet-draw-actions li { 181 | display: inline-block; 182 | } 183 | 184 | .leaflet-draw-actions li:first-child a { 185 | border-left: none; 186 | } 187 | 188 | .leaflet-draw-actions li:last-child a { 189 | -webkit-border-radius: 0 4px 4px 0; 190 | border-radius: 0 4px 4px 0; 191 | } 192 | 193 | .leaflet-right .leaflet-draw-actions li:last-child a { 194 | -webkit-border-radius: 0; 195 | border-radius: 0; 196 | } 197 | 198 | .leaflet-right .leaflet-draw-actions li:first-child a { 199 | -webkit-border-radius: 4px 0 0 4px; 200 | border-radius: 4px 0 0 4px; 201 | } 202 | 203 | .leaflet-draw-actions a { 204 | background-color: #919187; 205 | border-left: 1px solid #AAA; 206 | color: #FFF; 207 | font: 11px/19px "Helvetica Neue", Arial, Helvetica, sans-serif; 208 | line-height: 28px; 209 | text-decoration: none; 210 | padding-left: 10px; 211 | padding-right: 10px; 212 | height: 28px; 213 | } 214 | 215 | .leaflet-touch .leaflet-draw-actions a { 216 | font-size: 12px; 217 | line-height: 30px; 218 | height: 30px; 219 | } 220 | 221 | .leaflet-draw-actions-bottom { 222 | margin-top: 0; 223 | } 224 | 225 | .leaflet-draw-actions-top { 226 | margin-top: 1px; 227 | } 228 | 229 | .leaflet-draw-actions-top a, 230 | .leaflet-draw-actions-bottom a { 231 | height: 27px; 232 | line-height: 27px; 233 | } 234 | 235 | .leaflet-draw-actions a:hover { 236 | background-color: #A0A098; 237 | } 238 | 239 | .leaflet-draw-actions-top.leaflet-draw-actions-bottom a { 240 | height: 26px; 241 | line-height: 26px; 242 | } 243 | 244 | /* ================================================================== */ 245 | /* Draw toolbar 246 | /* ================================================================== */ 247 | 248 | .leaflet-draw-toolbar .leaflet-draw-draw-polyline { 249 | background-position: -2px -2px; 250 | } 251 | 252 | .leaflet-touch .leaflet-draw-toolbar .leaflet-draw-draw-polyline { 253 | background-position: 0 -1px; 254 | } 255 | 256 | .leaflet-draw-toolbar .leaflet-draw-draw-polygon { 257 | background-position: -31px -2px; 258 | } 259 | 260 | .leaflet-touch .leaflet-draw-toolbar .leaflet-draw-draw-polygon { 261 | background-position: -29px -1px; 262 | } 263 | 264 | .leaflet-draw-toolbar .leaflet-draw-draw-rectangle { 265 | background-position: -62px -2px; 266 | } 267 | 268 | .leaflet-touch .leaflet-draw-toolbar .leaflet-draw-draw-rectangle { 269 | background-position: -60px -1px; 270 | } 271 | 272 | .leaflet-draw-toolbar .leaflet-draw-draw-circle { 273 | background-position: -92px -2px; 274 | } 275 | 276 | .leaflet-touch .leaflet-draw-toolbar .leaflet-draw-draw-circle { 277 | background-position: -90px -1px; 278 | } 279 | 280 | .leaflet-draw-toolbar .leaflet-draw-draw-marker { 281 | background-position: -122px -2px; 282 | } 283 | 284 | .leaflet-touch .leaflet-draw-toolbar .leaflet-draw-draw-marker { 285 | background-position: -120px -1px; 286 | } 287 | 288 | .leaflet-draw-toolbar .leaflet-draw-draw-circlemarker { 289 | background-position: -273px -2px; 290 | } 291 | 292 | .leaflet-touch .leaflet-draw-toolbar .leaflet-draw-draw-circlemarker { 293 | background-position: -271px -1px; 294 | } 295 | 296 | /* ================================================================== */ 297 | /* Edit toolbar 298 | /* ================================================================== */ 299 | 300 | .leaflet-draw-toolbar .leaflet-draw-edit-edit { 301 | background-position: -152px -2px; 302 | } 303 | 304 | .leaflet-touch .leaflet-draw-toolbar .leaflet-draw-edit-edit { 305 | background-position: -150px -1px; 306 | } 307 | 308 | .leaflet-draw-toolbar .leaflet-draw-edit-remove { 309 | background-position: -182px -2px; 310 | } 311 | 312 | .leaflet-touch .leaflet-draw-toolbar .leaflet-draw-edit-remove { 313 | background-position: -180px -1px; 314 | } 315 | 316 | .leaflet-draw-toolbar .leaflet-draw-edit-edit.leaflet-disabled { 317 | background-position: -212px -2px; 318 | } 319 | 320 | .leaflet-touch .leaflet-draw-toolbar .leaflet-draw-edit-edit.leaflet-disabled { 321 | background-position: -210px -1px; 322 | } 323 | 324 | .leaflet-draw-toolbar .leaflet-draw-edit-remove.leaflet-disabled { 325 | background-position: -242px -2px; 326 | } 327 | 328 | .leaflet-touch .leaflet-draw-toolbar .leaflet-draw-edit-remove.leaflet-disabled { 329 | background-position: -240px -2px; 330 | } 331 | 332 | /* ================================================================== */ 333 | /* Drawing styles 334 | /* ================================================================== */ 335 | 336 | .leaflet-mouse-marker { 337 | background-color: #fff; 338 | cursor: crosshair; 339 | } 340 | 341 | .leaflet-draw-tooltip { 342 | background: rgb(54, 54, 54); 343 | background: rgba(0, 0, 0, 0.5); 344 | border: 1px solid transparent; 345 | -webkit-border-radius: 4px; 346 | border-radius: 4px; 347 | color: #fff; 348 | font: 12px/18px "Helvetica Neue", Arial, Helvetica, sans-serif; 349 | margin-left: 20px; 350 | margin-top: -21px; 351 | padding: 4px 8px; 352 | position: absolute; 353 | visibility: hidden; 354 | white-space: nowrap; 355 | z-index: 6; 356 | } 357 | 358 | .leaflet-draw-tooltip:before { 359 | border-right: 6px solid black; 360 | border-right-color: rgba(0, 0, 0, 0.5); 361 | border-top: 6px solid transparent; 362 | border-bottom: 6px solid transparent; 363 | content: ""; 364 | position: absolute; 365 | top: 7px; 366 | left: -7px; 367 | } 368 | 369 | .leaflet-error-draw-tooltip { 370 | background-color: #F2DEDE; 371 | border: 1px solid #E6B6BD; 372 | color: #B94A48; 373 | } 374 | 375 | .leaflet-error-draw-tooltip:before { 376 | border-right-color: #E6B6BD; 377 | } 378 | 379 | .leaflet-draw-tooltip-single { 380 | margin-top: -12px 381 | } 382 | 383 | .leaflet-draw-tooltip-subtext { 384 | color: #f8d5e4; 385 | } 386 | 387 | .leaflet-draw-guide-dash { 388 | font-size: 1%; 389 | opacity: 0.6; 390 | position: absolute; 391 | width: 5px; 392 | height: 5px; 393 | } 394 | 395 | /* ================================================================== */ 396 | /* Edit styles 397 | /* ================================================================== */ 398 | 399 | .leaflet-edit-marker-selected { 400 | background-color: rgba(254, 87, 161, 0.1); 401 | border: 4px dashed rgba(254, 87, 161, 0.6); 402 | -webkit-border-radius: 4px; 403 | border-radius: 4px; 404 | box-sizing: content-box; 405 | } 406 | 407 | .leaflet-edit-move { 408 | cursor: move; 409 | } 410 | 411 | .leaflet-edit-resize { 412 | cursor: pointer; 413 | } 414 | 415 | /* ================================================================== */ 416 | /* Old IE styles 417 | /* ================================================================== */ 418 | 419 | .leaflet-oldie .leaflet-draw-toolbar { 420 | border: 1px solid #999; 421 | } 422 | -------------------------------------------------------------------------------- /lunar_assembler_dependencies/leaflet-version0.7_references_to_layer_switcher_image_commented_out.css: -------------------------------------------------------------------------------- 1 | /* required styles */ 2 | 3 | .leaflet-map-pane, 4 | .leaflet-tile, 5 | .leaflet-marker-icon, 6 | .leaflet-marker-shadow, 7 | .leaflet-tile-pane, 8 | .leaflet-tile-container, 9 | .leaflet-overlay-pane, 10 | .leaflet-shadow-pane, 11 | .leaflet-marker-pane, 12 | .leaflet-popup-pane, 13 | .leaflet-overlay-pane svg, 14 | .leaflet-zoom-box, 15 | .leaflet-image-layer, 16 | .leaflet-layer { 17 | position: absolute; 18 | left: 0; 19 | top: 0; 20 | } 21 | .leaflet-container { 22 | overflow: hidden; 23 | -ms-touch-action: none; 24 | } 25 | .leaflet-tile, 26 | .leaflet-marker-icon, 27 | .leaflet-marker-shadow { 28 | -webkit-user-select: none; 29 | -moz-user-select: none; 30 | user-select: none; 31 | -webkit-user-drag: none; 32 | } 33 | .leaflet-marker-icon, 34 | .leaflet-marker-shadow { 35 | display: block; 36 | } 37 | /* map is broken in FF if you have max-width: 100% on tiles */ 38 | .leaflet-container img { 39 | max-width: none !important; 40 | } 41 | /* stupid Android 2 doesn't understand "max-width: none" properly */ 42 | .leaflet-container img.leaflet-image-layer { 43 | max-width: 15000px !important; 44 | } 45 | .leaflet-tile { 46 | filter: inherit; 47 | visibility: hidden; 48 | } 49 | .leaflet-tile-loaded { 50 | visibility: inherit; 51 | } 52 | .leaflet-zoom-box { 53 | width: 0; 54 | height: 0; 55 | } 56 | /* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */ 57 | .leaflet-overlay-pane svg { 58 | -moz-user-select: none; 59 | } 60 | 61 | .leaflet-tile-pane { z-index: 2; } 62 | .leaflet-objects-pane { z-index: 3; } 63 | .leaflet-overlay-pane { z-index: 4; } 64 | .leaflet-shadow-pane { z-index: 5; } 65 | .leaflet-marker-pane { z-index: 6; } 66 | .leaflet-popup-pane { z-index: 7; } 67 | 68 | .leaflet-vml-shape { 69 | width: 1px; 70 | height: 1px; 71 | } 72 | .lvml { 73 | behavior: url(#default#VML); 74 | display: inline-block; 75 | position: absolute; 76 | } 77 | 78 | 79 | /* control positioning */ 80 | 81 | .leaflet-control { 82 | position: relative; 83 | z-index: 7; 84 | pointer-events: auto; 85 | } 86 | .leaflet-top, 87 | .leaflet-bottom { 88 | position: absolute; 89 | z-index: 1000; 90 | pointer-events: none; 91 | } 92 | .leaflet-top { 93 | top: 0; 94 | } 95 | .leaflet-right { 96 | right: 0; 97 | } 98 | .leaflet-bottom { 99 | bottom: 0; 100 | } 101 | .leaflet-left { 102 | left: 0; 103 | } 104 | .leaflet-control { 105 | float: left; 106 | clear: both; 107 | } 108 | .leaflet-right .leaflet-control { 109 | float: right; 110 | } 111 | .leaflet-top .leaflet-control { 112 | margin-top: 10px; 113 | } 114 | .leaflet-bottom .leaflet-control { 115 | margin-bottom: 10px; 116 | } 117 | .leaflet-left .leaflet-control { 118 | margin-left: 10px; 119 | } 120 | .leaflet-right .leaflet-control { 121 | margin-right: 10px; 122 | } 123 | 124 | 125 | /* zoom and fade animations */ 126 | 127 | .leaflet-fade-anim .leaflet-tile, 128 | .leaflet-fade-anim .leaflet-popup { 129 | opacity: 0; 130 | -webkit-transition: opacity 0.2s linear; 131 | -moz-transition: opacity 0.2s linear; 132 | -o-transition: opacity 0.2s linear; 133 | transition: opacity 0.2s linear; 134 | } 135 | .leaflet-fade-anim .leaflet-tile-loaded, 136 | .leaflet-fade-anim .leaflet-map-pane .leaflet-popup { 137 | opacity: 1; 138 | } 139 | 140 | .leaflet-zoom-anim .leaflet-zoom-animated { 141 | -webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1); 142 | -moz-transition: -moz-transform 0.25s cubic-bezier(0,0,0.25,1); 143 | -o-transition: -o-transform 0.25s cubic-bezier(0,0,0.25,1); 144 | transition: transform 0.25s cubic-bezier(0,0,0.25,1); 145 | } 146 | .leaflet-zoom-anim .leaflet-tile, 147 | .leaflet-pan-anim .leaflet-tile, 148 | .leaflet-touching .leaflet-zoom-animated { 149 | -webkit-transition: none; 150 | -moz-transition: none; 151 | -o-transition: none; 152 | transition: none; 153 | } 154 | 155 | .leaflet-zoom-anim .leaflet-zoom-hide { 156 | visibility: hidden; 157 | } 158 | 159 | 160 | /* cursors */ 161 | 162 | .leaflet-clickable { 163 | cursor: pointer; 164 | } 165 | .leaflet-container { 166 | cursor: -webkit-grab; 167 | cursor: -moz-grab; 168 | } 169 | .leaflet-popup-pane, 170 | .leaflet-control { 171 | cursor: auto; 172 | } 173 | .leaflet-dragging .leaflet-container, 174 | .leaflet-dragging .leaflet-clickable { 175 | cursor: move; 176 | cursor: -webkit-grabbing; 177 | cursor: -moz-grabbing; 178 | } 179 | 180 | 181 | /* visual tweaks */ 182 | 183 | .leaflet-container { 184 | background: #ddd; 185 | outline: 0; 186 | } 187 | .leaflet-container a { 188 | color: #0078A8; 189 | } 190 | .leaflet-container a.leaflet-active { 191 | outline: 2px solid orange; 192 | } 193 | .leaflet-zoom-box { 194 | border: 2px dotted #38f; 195 | background: rgba(255,255,255,0.5); 196 | } 197 | 198 | 199 | /* general typography */ 200 | .leaflet-container { 201 | font: 12px/1.5 "Helvetica Neue", Arial, Helvetica, sans-serif; 202 | } 203 | 204 | 205 | /* general toolbar styles */ 206 | 207 | .leaflet-bar { 208 | box-shadow: 0 1px 5px rgba(0,0,0,0.65); 209 | border-radius: 4px; 210 | } 211 | .leaflet-bar a, 212 | .leaflet-bar a:hover { 213 | background-color: #fff; 214 | border-bottom: 1px solid #ccc; 215 | width: 26px; 216 | height: 26px; 217 | line-height: 26px; 218 | display: block; 219 | text-align: center; 220 | text-decoration: none; 221 | color: black; 222 | } 223 | .leaflet-bar a, 224 | .leaflet-control-layers-toggle { 225 | background-position: 50% 50%; 226 | background-repeat: no-repeat; 227 | display: block; 228 | } 229 | .leaflet-bar a:hover { 230 | background-color: #f4f4f4; 231 | } 232 | .leaflet-bar a:first-child { 233 | border-top-left-radius: 4px; 234 | border-top-right-radius: 4px; 235 | } 236 | .leaflet-bar a:last-child { 237 | border-bottom-left-radius: 4px; 238 | border-bottom-right-radius: 4px; 239 | border-bottom: none; 240 | } 241 | .leaflet-bar a.leaflet-disabled { 242 | cursor: default; 243 | background-color: #f4f4f4; 244 | color: #bbb; 245 | } 246 | 247 | .leaflet-touch .leaflet-bar a { 248 | width: 30px; 249 | height: 30px; 250 | line-height: 30px; 251 | } 252 | 253 | 254 | /* zoom control */ 255 | 256 | .leaflet-control-zoom-in, 257 | .leaflet-control-zoom-out { 258 | font: bold 18px 'Lucida Console', Monaco, monospace; 259 | text-indent: 1px; 260 | } 261 | .leaflet-control-zoom-out { 262 | font-size: 20px; 263 | } 264 | 265 | .leaflet-touch .leaflet-control-zoom-in { 266 | font-size: 22px; 267 | } 268 | .leaflet-touch .leaflet-control-zoom-out { 269 | font-size: 24px; 270 | } 271 | 272 | 273 | /* layers control */ 274 | 275 | .leaflet-control-layers { 276 | box-shadow: 0 1px 5px rgba(0,0,0,0.4); 277 | background: #fff; 278 | border-radius: 5px; 279 | } 280 | /* 281 | .leaflet-control-layers-toggle { 282 | background-image: url(images/layers.png); 283 | width: 36px; 284 | height: 36px; 285 | } 286 | .leaflet-retina .leaflet-control-layers-toggle { 287 | background-image: url(images/layers-2x.png); 288 | background-size: 26px 26px; 289 | } 290 | */ 291 | .leaflet-touch .leaflet-control-layers-toggle { 292 | width: 44px; 293 | height: 44px; 294 | } 295 | .leaflet-control-layers .leaflet-control-layers-list, 296 | .leaflet-control-layers-expanded .leaflet-control-layers-toggle { 297 | display: none; 298 | } 299 | .leaflet-control-layers-expanded .leaflet-control-layers-list { 300 | display: block; 301 | position: relative; 302 | } 303 | .leaflet-control-layers-expanded { 304 | padding: 6px 10px 6px 6px; 305 | color: #333; 306 | background: #fff; 307 | } 308 | .leaflet-control-layers-selector { 309 | margin-top: 2px; 310 | position: relative; 311 | top: 1px; 312 | } 313 | .leaflet-control-layers label { 314 | display: block; 315 | } 316 | .leaflet-control-layers-separator { 317 | height: 0; 318 | border-top: 1px solid #ddd; 319 | margin: 5px -10px 5px -6px; 320 | } 321 | 322 | 323 | /* attribution and scale controls */ 324 | 325 | .leaflet-container .leaflet-control-attribution { 326 | background: #fff; 327 | background: rgba(255, 255, 255, 0.7); 328 | margin: 0; 329 | } 330 | .leaflet-control-attribution, 331 | .leaflet-control-scale-line { 332 | padding: 0 5px; 333 | color: #333; 334 | } 335 | .leaflet-control-attribution a { 336 | text-decoration: none; 337 | } 338 | .leaflet-control-attribution a:hover { 339 | text-decoration: underline; 340 | } 341 | .leaflet-container .leaflet-control-attribution, 342 | .leaflet-container .leaflet-control-scale { 343 | font-size: 11px; 344 | } 345 | .leaflet-left .leaflet-control-scale { 346 | margin-left: 5px; 347 | } 348 | .leaflet-bottom .leaflet-control-scale { 349 | margin-bottom: 5px; 350 | } 351 | .leaflet-control-scale-line { 352 | border: 2px solid #777; 353 | border-top: none; 354 | line-height: 1.1; 355 | padding: 2px 5px 1px; 356 | font-size: 11px; 357 | white-space: nowrap; 358 | overflow: hidden; 359 | -moz-box-sizing: content-box; 360 | box-sizing: content-box; 361 | 362 | background: #fff; 363 | background: rgba(255, 255, 255, 0.5); 364 | } 365 | .leaflet-control-scale-line:not(:first-child) { 366 | border-top: 2px solid #777; 367 | border-bottom: none; 368 | margin-top: -2px; 369 | } 370 | .leaflet-control-scale-line:not(:first-child):not(:last-child) { 371 | border-bottom: 2px solid #777; 372 | } 373 | 374 | .leaflet-touch .leaflet-control-attribution, 375 | .leaflet-touch .leaflet-control-layers, 376 | .leaflet-touch .leaflet-bar { 377 | box-shadow: none; 378 | } 379 | .leaflet-touch .leaflet-control-layers, 380 | .leaflet-touch .leaflet-bar { 381 | border: 2px solid rgba(0,0,0,0.2); 382 | background-clip: padding-box; 383 | } 384 | 385 | 386 | /* popup */ 387 | 388 | .leaflet-popup { 389 | position: absolute; 390 | text-align: center; 391 | } 392 | .leaflet-popup-content-wrapper { 393 | padding: 1px; 394 | text-align: left; 395 | border-radius: 12px; 396 | } 397 | .leaflet-popup-content { 398 | margin: 13px 19px; 399 | line-height: 1.4; 400 | } 401 | .leaflet-popup-content p { 402 | margin: 18px 0; 403 | } 404 | .leaflet-popup-tip-container { 405 | margin: 0 auto; 406 | width: 40px; 407 | height: 20px; 408 | position: relative; 409 | overflow: hidden; 410 | } 411 | .leaflet-popup-tip { 412 | width: 17px; 413 | height: 17px; 414 | padding: 1px; 415 | 416 | margin: -10px auto 0; 417 | 418 | -webkit-transform: rotate(45deg); 419 | -moz-transform: rotate(45deg); 420 | -ms-transform: rotate(45deg); 421 | -o-transform: rotate(45deg); 422 | transform: rotate(45deg); 423 | } 424 | .leaflet-popup-content-wrapper, 425 | .leaflet-popup-tip { 426 | background: white; 427 | 428 | box-shadow: 0 3px 14px rgba(0,0,0,0.4); 429 | } 430 | .leaflet-container a.leaflet-popup-close-button { 431 | position: absolute; 432 | top: 0; 433 | right: 0; 434 | padding: 4px 4px 0 0; 435 | text-align: center; 436 | width: 18px; 437 | height: 14px; 438 | font: 16px/14px Tahoma, Verdana, sans-serif; 439 | color: #c3c3c3; 440 | text-decoration: none; 441 | font-weight: bold; 442 | background: transparent; 443 | } 444 | .leaflet-container a.leaflet-popup-close-button:hover { 445 | color: #999; 446 | } 447 | .leaflet-popup-scrolled { 448 | overflow: auto; 449 | border-bottom: 1px solid #ddd; 450 | border-top: 1px solid #ddd; 451 | } 452 | 453 | .leaflet-oldie .leaflet-popup-content-wrapper { 454 | zoom: 1; 455 | } 456 | .leaflet-oldie .leaflet-popup-tip { 457 | width: 24px; 458 | margin: 0 auto; 459 | 460 | -ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)"; 461 | filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678); 462 | } 463 | .leaflet-oldie .leaflet-popup-tip-container { 464 | margin-top: -1px; 465 | } 466 | 467 | .leaflet-oldie .leaflet-control-zoom, 468 | .leaflet-oldie .leaflet-control-layers, 469 | .leaflet-oldie .leaflet-popup-content-wrapper, 470 | .leaflet-oldie .leaflet-popup-tip { 471 | border: 1px solid #999; 472 | } 473 | 474 | 475 | /* div icon */ 476 | 477 | .leaflet-div-icon { 478 | background: #fff; 479 | border: 1px solid #666; 480 | } 481 | -------------------------------------------------------------------------------- /lunar_assembler_helpful_functions_for_map_styles.js: -------------------------------------------------------------------------------- 1 | /* 2 | lunar_assembler - tool for generating SVG files from OpenStreetMap data. Available as a website. 3 | Copyright (C) 2021 Mateusz Konieczny 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Affero General Public License as 7 | published by the Free Software Foundation, under version 3 of the 8 | License only. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU Affero General Public License for more details. 14 | 15 | You should have received a copy of the GNU Affero General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | function findMergeGroupObject(dataGeojson, code) { 20 | var i = dataGeojson.features.length; 21 | var found = undefined; 22 | while (i--) { 23 | var feature = dataGeojson.features[i]; 24 | //lunar_assembler_merge_group is applied by lunar assembler, see mergeAsRequestedByMapStyle function 25 | if (feature.properties["lunar_assembler_merge_group"] == code) { 26 | if (found != undefined) { 27 | showError("more than one area of " + code + " type what is unexpected, things may break. This is a bug, please report it on https://github.com/matkoniecz/lunar_assembler/issues"); 28 | } 29 | found = feature; 30 | } 31 | } 32 | if (found == undefined) { 33 | console.warn("findMergeGroupObject failed to find " + code + " - if not expected please report at https://github.com/matkoniecz/lunar_assembler/issues"); 34 | } 35 | return found; 36 | } 37 | -------------------------------------------------------------------------------- /lunar_assembler_helpful_functions_for_map_styles_apply_patterns_to_areas.js: -------------------------------------------------------------------------------- 1 | /* 2 | lunar_assembler - tool for generating SVG files from OpenStreetMap data. Available as a website. 3 | Copyright (C) 2021 Mateusz Konieczny 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Affero General Public License as 7 | published by the Free Software Foundation, under version 3 of the 8 | License only. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU Affero General Public License for more details. 14 | 15 | You should have received a copy of the GNU Affero General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | function intersectGeometryWithHorizontalStripes(feature, stripeSizeInDegrees, distanceBetweenStripesInDegrees) { 20 | bbox = turf.bbox(feature); 21 | var minLongitude = bbox[0]; 22 | var minLatitude = bbox[1]; 23 | var maxLongitude = bbox[2]; 24 | var maxLatitude = bbox[3]; 25 | if (!isMultipolygonAsExpected(feature)) { 26 | return null; 27 | } 28 | var collected = []; 29 | // gathering horizontal stripes 30 | var minLatitudeForStripe = minLatitude; 31 | while (minLatitudeForStripe < maxLatitude) { 32 | var maxLatitudeForStripe = minLatitudeForStripe + stripeSizeInDegrees; 33 | var stripeRing = [ 34 | [minLongitude, minLatitudeForStripe], 35 | [maxLongitude, minLatitudeForStripe], 36 | [maxLongitude, maxLatitudeForStripe], 37 | [minLongitude, maxLatitudeForStripe], 38 | [minLongitude, minLatitudeForStripe], 39 | ]; 40 | var stripe = [stripeRing]; 41 | var intersectedStripe = polygonClipping.intersection(feature.geometry.coordinates, stripe); 42 | if (intersectedStripe != []) { 43 | collected.push(intersectedStripe); 44 | } 45 | minLatitudeForStripe += stripeSizeInDegrees + distanceBetweenStripesInDegrees; 46 | } 47 | if (collected.length == 1) { 48 | console.warn("one element! Is spread working as expected? See #68"); // TODO - trigger and debug it 49 | } 50 | var generated = polygonClipping.union(...collected); 51 | 52 | var cloned = JSON.parse(JSON.stringify(feature)); 53 | cloned.geometry.coordinates = generated; 54 | return cloned; 55 | } 56 | 57 | function intersectGeometryWithVerticalStripes(feature, stripeSizeInDegrees, distanceBetweenStripesInDegrees) { 58 | bbox = turf.bbox(feature); 59 | var minLongitude = bbox[0]; 60 | var minLatitude = bbox[1]; 61 | var maxLongitude = bbox[2]; 62 | var maxLatitude = bbox[3]; 63 | if (!isMultipolygonAsExpected(feature)) { 64 | return null; 65 | } 66 | var collected = []; 67 | // gathering horizontal stripes 68 | var minLongitudeForStripe = minLongitude; 69 | while (minLongitudeForStripe < maxLongitude) { 70 | var maxLongitudeForStripe = minLongitudeForStripe + stripeSizeInDegrees; 71 | var stripeRing = [ 72 | [minLongitudeForStripe, minLatitude], 73 | [maxLongitudeForStripe, minLatitude], 74 | [maxLongitudeForStripe, maxLatitude], 75 | [minLongitudeForStripe, maxLatitude], 76 | [minLongitudeForStripe, minLatitude], 77 | ]; 78 | var stripe = [stripeRing]; 79 | var intersectedStripe = polygonClipping.intersection(feature.geometry.coordinates, stripe); 80 | if (intersectedStripe != []) { 81 | collected.push(intersectedStripe); 82 | } 83 | minLongitudeForStripe += stripeSizeInDegrees + distanceBetweenStripesInDegrees; 84 | } 85 | if (collected.length == 1) { 86 | console.warn("one element! Is spread working as expected? See #68"); // TODO - trigger and debug it 87 | } 88 | var generated = polygonClipping.union(...collected); 89 | 90 | var cloned = JSON.parse(JSON.stringify(feature)); 91 | cloned.geometry.coordinates = generated; 92 | return cloned; 93 | } 94 | 95 | 96 | function intersectGeometryWithPlaneHavingRectangularHoles(feature, holeVerticalInDegrees, holeHorizontalInDegrees, spaceVerticalInDegrees, spaceHorizontalInDegrees) { 97 | bbox = turf.bbox(feature); 98 | var minLongitude = bbox[0]; 99 | var minLatitude = bbox[1]; 100 | var maxLongitude = bbox[2]; 101 | var maxLatitude = bbox[3]; 102 | if (!isMultipolygonAsExpected(feature)) { 103 | return null; 104 | } 105 | var collected = []; 106 | // gathering horizontal stripes 107 | var minLatitudeForStripe = minLatitude; 108 | while (minLatitudeForStripe < maxLatitude) { 109 | var maxLatitudeForStripe = minLatitudeForStripe + spaceVerticalInDegrees; 110 | var stripeRing = [ 111 | [minLongitude, minLatitudeForStripe], 112 | [maxLongitude, minLatitudeForStripe], 113 | [maxLongitude, maxLatitudeForStripe], 114 | [minLongitude, maxLatitudeForStripe], 115 | [minLongitude, minLatitudeForStripe], 116 | ]; 117 | var stripe = [stripeRing]; 118 | var intersectedStripe = polygonClipping.intersection(feature.geometry.coordinates, stripe); 119 | if (intersectedStripe.length > 0) { 120 | collected.push(intersectedStripe); 121 | } 122 | minLatitudeForStripe += spaceVerticalInDegrees + holeVerticalInDegrees; 123 | } 124 | if (collected.length == 1) { 125 | console.warn("one element! Is spread working as expected? See #68"); // TODO - trigger and debug it 126 | } 127 | // split in pairs due to https://github.com/mfogel/polygon-clipping/issues/118 128 | var generatedHorizontal = polygonClipping.union(...collected); 129 | collected = []; 130 | 131 | // gathering vertical stripes 132 | var minLongitudeForStripe = minLongitude; 133 | while (minLongitudeForStripe < maxLongitude) { 134 | var maxLongitudeForStripe = minLongitudeForStripe + spaceHorizontalInDegrees; 135 | var stripeRing = [ 136 | [minLongitudeForStripe, minLatitude], 137 | [maxLongitudeForStripe, minLatitude], 138 | [maxLongitudeForStripe, maxLatitude], 139 | [minLongitudeForStripe, maxLatitude], 140 | [minLongitudeForStripe, minLatitude], 141 | ]; 142 | var stripe = [stripeRing]; 143 | var intersectedStripe = polygonClipping.intersection(feature.geometry.coordinates, stripe); 144 | if (intersectedStripe.length > 0) { 145 | collected.push(intersectedStripe); 146 | } 147 | minLongitudeForStripe += spaceHorizontalInDegrees + holeHorizontalInDegrees; 148 | } 149 | if (collected.length == 1) { 150 | console.warn("one element! Is spread working as expected? See #68"); // TODO - trigger and debug it 151 | } 152 | var generatedVertical = polygonClipping.union(...collected); 153 | var generated = polygonClipping.union(generatedHorizontal, generatedVertical); 154 | 155 | var cloned = JSON.parse(JSON.stringify(feature)); 156 | cloned.geometry.coordinates = generated; 157 | return cloned; 158 | } 159 | -------------------------------------------------------------------------------- /lunar_assembler_helpful_functions_for_map_styles_generate_inaccessible_areas.js: -------------------------------------------------------------------------------- 1 | /* 2 | lunar_assembler - tool for generating SVG files from OpenStreetMap data. Available as a website. 3 | Copyright (C) 2021 Mateusz Konieczny 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Affero General Public License as 7 | published by the Free Software Foundation, under version 3 of the 8 | License only. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU Affero General Public License for more details. 14 | 15 | You should have received a copy of the GNU Affero General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | /* 20 | This file contains code to generate entire inaccessible areas as geometries. 21 | 22 | This way it is possible to generalize building and barriers into one area. 23 | 24 | Useful when one does not care about internal courtyards, what exactly is in walled of gardens etc. 25 | 26 | to use this code: 27 | 28 | add to transformGeometryAsInitialStep() 29 | 30 | dataGeojson = generateAreasFromBarriers(dataGeojson); 31 | dataGeojson = generateRestrictedAcccessArea(dataGeojson, readableBounds); 32 | 33 | add to unifiedStyling() 34 | 35 | const barrierAreaColor = "#b76b80"; 36 | const generatedImpassableAreaColor = "black"; 37 | returned = addRulesForDisplayOfCalculatedImpassableArea(returned, barrierAreaColor, generatedImpassableAreaColor); 38 | 39 | obviously, colors in configuration here can be changed! 40 | */ 41 | 42 | function restrictiveAccessValues() { 43 | return ['no', 'private', 'customers']; 44 | } 45 | 46 | function isAccessValueRestrictive(value) { 47 | if(restrictiveAccessValues().indexOf(value) >= 0) { 48 | return true; 49 | } 50 | return false; 51 | } 52 | 53 | function listOfTagSetsBlockingPedestrianAccess() { 54 | returned = [ 55 | {'natural': 'water'}, 56 | {'waterway': 'riverbank'}, 57 | {'building': undefined}, 58 | {'waterway': 'riverbank'}, 59 | {'area:highway': undefined, 'foot': 'no'}, 60 | ] 61 | /* 62 | pulling parking rules here may be nice but beginning to be ridiculous 63 | for (const restrictive of restrictiveAccessValues()) { 64 | 65 | } 66 | */ 67 | return returned; 68 | } 69 | 70 | 71 | function generateRestrictedAcccessArea(geojson, readableBounds) { 72 | var entireArea = readableBoundsToGeojsonGeometry(readableBounds); 73 | 74 | // clipping with empty area is done here to ensure that multipolygon 75 | // is always produced, also when no forbidden areas are present 76 | var areaOfUnknownState = polygonClipping.difference(entireArea, []); 77 | 78 | // TODO TODO TODO after moving everything 79 | //generated = cloneAndCollectAreasDirectlyBlockingPedestrianMovement(geojson) 80 | 81 | generated = []; 82 | featuresGivingAccess = []; 83 | var i = geojson.features.length; 84 | while (i--) { 85 | var feature = geojson.features[i]; 86 | if (isFeatureMakingFreePedestrianMovementPossible(feature)) { 87 | featuresGivingAccess.push(feature); 88 | } 89 | const link = "https://www.openstreetmap.org/" + feature.id; 90 | if (feature.geometry.type != "Polygon" && feature.geometry.type != "MultiPolygon") { 91 | continue; 92 | } 93 | if (isAreaMakingFreePedestrianMovementImpossible(feature)) { 94 | var areaOfUnknownState = polygonClipping.difference(areaOfUnknownState, feature.geometry.coordinates); 95 | if (feature.properties["natural"] != "water" && feature.properties["waterway"] != "riverbank") { 96 | // water has its own special rendering and does not need this 97 | var cloned = JSON.parse(JSON.stringify(feature)); 98 | cloned.properties = { native_blocked_chunk: "yes" }; 99 | generated.push(cloned); 100 | } 101 | } 102 | } 103 | 104 | // TODO TODO TODO 105 | // TODO TODO TODO 106 | // apply areaOfUnknownState in iteration here 107 | 108 | //console.warn("areaOfUnknownState") 109 | //console.warn(JSON.stringify({ type: "MultiPolygon", coordinates: areaOfUnknownState })); 110 | 111 | // areaOfUnknownState is now entire area except removed blocking areas 112 | // now the next step is to fill areas where there is no access 113 | // for example private courtyard within buildings, walled of areas and so on 114 | 115 | var k = areaOfUnknownState.length; 116 | while (k--) { 117 | const traversableChunk = { 118 | type: "Feature", 119 | geometry: { type: "Polygon", coordinates: areaOfUnknownState[k] }, 120 | properties: {}, 121 | }; 122 | var i = featuresGivingAccess.length; 123 | while (i--) { 124 | const accessGivingFeature = featuresGivingAccess[i]; 125 | 126 | if (turf.lineIntersect(traversableChunk, accessGivingFeature).features.length != 0) { 127 | traversableChunk.properties["generated_traversable_chunk"] = "yes"; 128 | break; 129 | } 130 | } 131 | if (traversableChunk.properties["generated_traversable_chunk"] != "yes") { 132 | traversableChunk.properties["generated_blocked_chunk"] = "yes"; 133 | } 134 | geojson.features.push(traversableChunk); 135 | } 136 | var k = generated.length; 137 | while (k--) { 138 | geojson.features.push(generated[k]); 139 | } 140 | return geojson; 141 | } 142 | 143 | function cloneAndCollectAreasDirectlyBlockingPedestrianMovement(geojson) { 144 | 145 | } 146 | 147 | function readableBoundsToGeojsonGeometry(readableBounds) { 148 | var entireAreaRing = [ 149 | [readableBounds["east"], readableBounds["south"]], 150 | [readableBounds["east"], readableBounds["north"]], 151 | [readableBounds["west"], readableBounds["north"]], 152 | [readableBounds["west"], readableBounds["south"]], 153 | [readableBounds["east"], readableBounds["south"]], 154 | ]; 155 | return [entireAreaRing]; 156 | } 157 | 158 | function generateAreasFromBarriers(geojson) { 159 | generated = []; 160 | var i = geojson.features.length; 161 | while (i--) { 162 | var feature = geojson.features[i]; 163 | const link = "https://www.openstreetmap.org/" + feature.id; 164 | 165 | if (linearGenerallyImpassableBarrierValuesArray().includes(feature.properties["barrier"]) || feature.properties["barrier"] == "yes") { 166 | var produced = turf.buffer(feature, 0.1, { units: "meters" }); 167 | var cloned = JSON.parse(JSON.stringify(produced)); 168 | cloned.properties["generated_barrier_area"] = "yes"; 169 | generated.push(cloned); 170 | } 171 | } 172 | 173 | var k = generated.length; 174 | while (k--) { 175 | geojson.features.push(generated[k]); 176 | } 177 | return geojson; 178 | } 179 | 180 | function isFeatureMakingFreePedestrianMovementPossible(feature) { 181 | // TODO right now it is missing from legend and taginfo entries as 182 | // this rules compile to something obnoxiously complex 183 | // it would be nice to list it 184 | // at least partially 185 | var highway = feature.properties["highway"]; 186 | var foot = feature.properties["foot"]; 187 | var access = feature.properties["access"]; 188 | if (motorizedRoadValuesArray().includes(highway) || ["footway", "pedestrian", "path", "steps", "cycleway"].includes(highway)) { 189 | if (isAccessValueRestrictive(foot)) { 190 | return false; 191 | } 192 | if (highway == "motorway" && foot == null) { 193 | // assume no for motorways, but do not discard them completely: some can be walked on foot (yes really) 194 | return false; 195 | } 196 | if (highway == "service" && feature.properties["service"] == "driveway") { 197 | if (foot != null && !isAccessValueRestrictive(foot)) { 198 | return true; 199 | } 200 | if (access != null && !isAccessValueRestrictive(access)) { 201 | return true; 202 | } 203 | return false; // assume false for driveways 204 | } 205 | 206 | if (!isAccessValueRestrictive(access) && !isAccessValueRestrictive(foot)) { 207 | return true; 208 | } 209 | if (isAccessValueRestrictive(access)) { 210 | if (foot != null && !isAccessValueRestrictive(foot)) { 211 | return true; 212 | } else { 213 | return false; 214 | } 215 | } 216 | showError("Should be impossible [isFeatureMakingFreePedestrianMovementPossible for " + JSON.stringify(feature) + "]," + reportBugMessage()); 217 | } 218 | } 219 | 220 | function isAreaMakingFreePedestrianMovementImpossible(feature) { 221 | if (feature.properties["generated_barrier_area"] != null) { 222 | return true; 223 | } 224 | const blockingTags = listOfTagSetsBlockingPedestrianAccess(); 225 | if(isMatchingAnyEntryInBlockingTagList(feature, blockingTags)) { 226 | return true; 227 | } 228 | if (isAccessValueRestrictive(feature.properties["access"]) && feature.properties["amenity"] != "parking") { 229 | if (feature.properties["foot"] == null || isAccessValueRestrictive(feature.properties["foot"])) { 230 | // TODO right now it is missing from legend and taginfo entries as 231 | // this rules compile to something obnoxiously complex 232 | return true; 233 | } 234 | } 235 | return false; 236 | } 237 | 238 | function isMatchingEntryInBlockingTags(feature, tagEntry) { 239 | for (const [key, value] of Object.entries(tagEntry)) { 240 | if(value == undefined) { 241 | // just checking presence of this key 242 | if(feature.properties[key] == null) { 243 | return false; 244 | } 245 | } else { 246 | if(feature.properties[key] != value) { 247 | return false; 248 | } 249 | } 250 | } 251 | return true; 252 | } 253 | 254 | function isMatchingAnyEntryInBlockingTagList(feature, blockingTagList) { 255 | for (const blockingTags of blockingTagList) { 256 | if(isMatchingEntryInBlockingTags(feature, blockingTags)) { 257 | return true; 258 | } 259 | } 260 | return false; 261 | } 262 | 263 | function addRulesForDisplayOfCalculatedImpassableArea(returned, barrierAreaColor, generatedImpassableAreaColor) { 264 | var barriersKeyValue = []; 265 | var i = linearGenerallyImpassableBarrierValuesArray().length; 266 | while (i--) { 267 | value = linearGenerallyImpassableBarrierValuesArray()[i]; 268 | barriersKeyValue.push({ key: "barrier", value: value, purpose: "generally impassable barrier, for detecting where access is blocked" }); 269 | } 270 | barriersKeyValue.push({ key: "barrier", value: "yes", purpose: "unknown barrier, assumed to be generally impassable barrier, for detecting where access is blocked" }); 271 | 272 | returned.push({ 273 | area_color: barrierAreaColor, 274 | description: "generated barrier areas", 275 | automatically_generated_using: barriersKeyValue, 276 | matches: [{ key: "generated_barrier_area", value: "yes" }], 277 | }); 278 | 279 | var blockDetection = JSON.parse(JSON.stringify(barriersKeyValue)); 280 | 281 | var message = "generally impassable barrier, for detecting where access is blocked" 282 | for (const blockingTags of listOfTagSetsBlockingPedestrianAccess()) { 283 | for (const [key, value] of Object.entries(blockingTags)) { 284 | if(value == undefined) { 285 | blockDetection.push({ "key": key, purpose: message }); 286 | } else { 287 | blockDetection.push({ "key": key, "value": value, purpose: message }); 288 | } 289 | } 290 | } 291 | 292 | returned.push({ 293 | area_color: generatedImpassableAreaColor, 294 | description: "areas that are inaccessible, generated automatically", 295 | automatically_generated_using: blockDetection, 296 | matches: [ 297 | { key: "generated_blocked_chunk", value: "yes" }, 298 | { key: "native_blocked_chunk", value: "yes" }, 299 | ], 300 | }); 301 | // generated_traversable_chunk=yes is not rendered 302 | return returned; 303 | } -------------------------------------------------------------------------------- /lunar_assembler_helpful_functions_for_map_styles_generate_symbolic_steps_from_area_highway.js: -------------------------------------------------------------------------------- 1 | /* 2 | lunar_assembler - tool for generating SVG files from OpenStreetMap data. Available as a website. 3 | Copyright (C) 2021 Mateusz Konieczny 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Affero General Public License as 7 | published by the Free Software Foundation, under version 3 of the 8 | License only. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU Affero General Public License for more details. 14 | 15 | You should have received a copy of the GNU Affero General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | // for use in unified map style, this allows to avoid a pointess duplications 20 | function unifiedMapStyleSegmentForSymbolicStepRepresentation() { 21 | const stepGenerationExplanation = [ 22 | { key: "area:highway", value: "steps", purpose: "area of steps, for an automatic generation of a symbolic representation" }, 23 | { key: "highway", value: "steps", purpose: "detecting upper/lower side of steps, for an automatic generation of a symbolic representation" }, 24 | { key: "incline", value: "up", purpose: "detecting upper/lower side of steps, for an automatic generation of a symbolic representation" }, 25 | { key: "incline", value: "down", purpose: "detecting upper/lower side of steps, for an automatic generation of a symbolic representation" }, 26 | ]; 27 | return [ 28 | { 29 | area_color: "#400080", 30 | description: "step segment, part of a symbolic steps representation - automatically generated (the lowest one, 4th from the top)", 31 | automatically_generated_using: stepGenerationExplanation, 32 | matches: [{ key: "lunar_assembler_step_segment", value: "0" }], 33 | }, 34 | { 35 | area_color: "magenta", 36 | description: "step segment, part of a symbolic steps representation - automatically generated (2nd from the bottom, 3rd from the top)", 37 | automatically_generated_using: stepGenerationExplanation, 38 | matches: [{ key: "lunar_assembler_step_segment", value: "1" }], 39 | }, 40 | { 41 | area_color: "#ff0000", 42 | description: "step segment, part of symbolic steps representation - automatically generated (3rd from the bottom, 2nd from the top)", 43 | automatically_generated_using: stepGenerationExplanation, 44 | matches: [{ key: "lunar_assembler_step_segment", value: "2" }], 45 | }, 46 | { 47 | area_color: "#D33F6A", 48 | description: "step segment - automatically generated - automatically generated (4th from the bottom, the highest one)", 49 | automatically_generated_using: stepGenerationExplanation, 50 | matches: [{ key: "lunar_assembler_step_segment", value: "3" }], 51 | }, 52 | ]; 53 | } 54 | 55 | function programaticallyGenerateSymbolicStepParts(dataGeojson) { 56 | var pointsInSteps = dataToListOfPositionOfStepsNodes(dataGeojson); 57 | var i = dataGeojson.features.length; 58 | var generatedFeatures = []; 59 | while (i--) { 60 | var feature = dataGeojson.features[i]; 61 | const link = "https://www.openstreetmap.org/" + feature.id; 62 | if (feature.properties["area:highway"] != "steps") { 63 | continue; 64 | } 65 | const rings = feature.geometry.coordinates.length; 66 | if (rings != 1) { 67 | showError( 68 | "untested for polygons with holes. And it seems that it should be represented as two highway=steps and two area:highway anyway. See " + 69 | link + 70 | "\nIf OSM data is correct and output is broken, please report to https://github.com/matkoniecz/lunar_assembler/issues" 71 | ); 72 | } 73 | var newFeaturesForAdding = buildAreasSplittingStepAreaIntoSymbolicSteps(feature, pointsInSteps); 74 | if (newFeaturesForAdding != null) { 75 | k = newFeaturesForAdding.length; 76 | while (k--) { 77 | generatedFeatures.push(newFeaturesForAdding[k]); 78 | } 79 | } 80 | } 81 | i = generatedFeatures.length; 82 | while (i--) { 83 | dataGeojson.features.push(generatedFeatures[i]); 84 | } 85 | return dataGeojson; 86 | } 87 | 88 | //////////////////////////////////////////// 89 | // steps processing 90 | function dataToListOfPositionOfStepsNodes(geojson) { 91 | // TODO: document is the first on list lower or higher 92 | pointsInSteps = []; 93 | var i = geojson.features.length; 94 | while (i--) { 95 | var feature = geojson.features[i]; 96 | const link = "https://www.openstreetmap.org/" + feature.id; 97 | if (feature.properties["highway"] == "steps") { 98 | if (feature.properties["area"] == "yes" || feature.properties["type"] === "multipolygon") { 99 | showFatalError("steps mapped as an area should use area:highway=steps tagging, " + link + " needs fixing"); 100 | } else if (feature.geometry.type != "LineString") { 101 | showFatalError("Unexpected geometry for steps, expected a LineString, got " + feature.geometry.type + " " + link + " needs fixing"); 102 | } else { 103 | var k = feature.geometry.coordinates.length; 104 | if (feature.properties["incline"] == "down") { 105 | // reverse order (assumes incline=up to be default) 106 | index = 0; 107 | while (index < k) { 108 | pointsInSteps.push(feature.geometry.coordinates[index]); 109 | index += 1; 110 | } 111 | } else { 112 | while (k--) { 113 | pointsInSteps.push(feature.geometry.coordinates[k]); 114 | } 115 | } 116 | } 117 | } 118 | } 119 | return pointsInSteps; 120 | } 121 | 122 | function buildAreasSplittingStepAreaIntoSymbolicSteps(feature, pointsInSteps) { 123 | // gets feature (area:highway=steps) and list of points in highway=steps 124 | // returns array of features with extra shapes giving symbolic depiction of steps 125 | 126 | // we can detect connecting nodes. Lets assume simplest case: 127 | // two nodes where highway=steps are connected, without substantially changing geometry 128 | // and area:highway has four more nodes for depicting steps geometry 129 | // so, for given feature we can detect skeleton with two ways forming sides of steps 130 | // this can be split into parts and form the expected steps 131 | // 132 | // it will fail for more complicated steps! 133 | // unit testing would be useful... 134 | // write just standalone code for now? not with some testing framework? 135 | 136 | const link = "https://www.openstreetmap.org/" + feature.id; 137 | var matches = indexesOfPointsWhichAreConnectedToStepsWay(feature, pointsInSteps); 138 | if (matches === null) { 139 | showFatalError("unable to build steps pattern for " + link + " - please create an issue at https://github.com/matkoniecz/lunar_assembler/issues if that is unexpected and unwanted"); 140 | return null; 141 | } 142 | var nodeCountOnPolygon = feature.geometry.coordinates[0].length; 143 | expectStepsPolygonCountToBeSixNodes(nodeCountOnPolygon, link); 144 | 145 | var pointBetweenStarts = feature.geometry.coordinates[0][matches[0].indexInObject]; 146 | var pointBetweenEnds = feature.geometry.coordinates[0][matches[0].indexInObject]; 147 | 148 | var firstLineStartIndex = (matches[0].indexInObject - 1) % nodeCountOnPolygon; 149 | var firstLineStart = feature.geometry.coordinates[0][firstLineStartIndex]; 150 | var firstLineEndIndex = (matches[1].indexInObject + 1) % nodeCountOnPolygon; 151 | var firstLineEnd = feature.geometry.coordinates[0][firstLineEndIndex]; 152 | 153 | var secondLineStartIndex = (matches[0].indexInObject + 1) % nodeCountOnPolygon; 154 | var secondLineStart = feature.geometry.coordinates[0][secondLineStartIndex]; 155 | var secondLineEndIndex = (matches[1].indexInObject - 1) % nodeCountOnPolygon; 156 | var secondLineEnd = feature.geometry.coordinates[0][secondLineEndIndex]; 157 | 158 | return buildAreasSplittingStepAreaIntoSymbolicStepsFromProvidedSkeletonLines(firstLineStart, firstLineEnd, secondLineStart, secondLineEnd, pointBetweenStarts, pointBetweenEnds); 159 | } 160 | 161 | function indexOfMatchingPointInArray(point, array) { 162 | var indexOfMatchingPointInSteps = -1; 163 | var stepIndex = array.length; 164 | while (stepIndex--) { 165 | if (point[0] === array[stepIndex][0] && point[1] === array[stepIndex][1]) { 166 | indexOfMatchingPointInSteps = stepIndex; 167 | return stepIndex; 168 | } 169 | } 170 | return -1; 171 | } 172 | 173 | function expectStepsPolygonCountToBeSixNodes(nodeCountOnPolygon, link) { 174 | const expected = 6 + 1; // +1 as a border node is repeated 175 | if (nodeCountOnPolygon != expected) { 176 | if (nodeCountOnPolygon > expected) { 177 | showError( 178 | "untested for large (" + 179 | nodeCountOnPolygon + 180 | " nodes) area:highway=steps geometries with more than 6 nodes. See " + 181 | link + 182 | "\nIf OSM data is correct and output is broken, please report to https://github.com/matkoniecz/lunar_assembler/issues" 183 | ); 184 | } else { 185 | showFatalError("unexpectedly low node count ( " + nodeCountOnPolygon + "), is highway=steps attached to area:highway=steps? See " + link); 186 | } 187 | } 188 | } 189 | 190 | function indexesOfPointsWhichAreConnectedToStepsWay(feature, pointsInSteps) { 191 | const link = "https://www.openstreetmap.org/" + feature.id; 192 | if (feature.geometry.type != "Polygon") { 193 | showFatalError( 194 | "unsupported for " + feature.geometry.type + "! Skipping, see " + link + "\nIf OSM data is correct and output is broken, please report to https://github.com/matkoniecz/lunar_assembler/issues" 195 | ); 196 | return null; 197 | } 198 | var nodeCountOnPolygon = feature.geometry.coordinates[0].length; 199 | expectStepsPolygonCountToBeSixNodes(nodeCountOnPolygon, link); 200 | var nodeIndex = nodeCountOnPolygon; 201 | var theFirstIntersection = undefined; 202 | var theSecondIntersection = undefined; 203 | while (nodeIndex-- > 1) { 204 | // > 1 is necessary as the last one is repetition of the first one 205 | const point = feature.geometry.coordinates[0][nodeIndex]; 206 | 207 | indexOfMatchingPointInSteps = indexOfMatchingPointInArray(point, pointsInSteps); 208 | if (indexOfMatchingPointInSteps != -1) { 209 | if (theFirstIntersection == undefined) { 210 | theFirstIntersection = { indexInObject: nodeIndex, indexInStepsArray: indexOfMatchingPointInSteps }; 211 | } else if (theSecondIntersection == undefined) { 212 | theSecondIntersection = { indexInObject: nodeIndex, indexInStepsArray: indexOfMatchingPointInSteps }; 213 | } else { 214 | showFatalError("more than 2 intersections of area:highway=steps with highway=steps, at " + link + "\nOSM data needs fixing."); 215 | } 216 | } 217 | } 218 | if (theFirstIntersection == undefined || theSecondIntersection == undefined) { 219 | showFatalError( 220 | "expected 2 intersections of area:highway=steps with highway=steps, got less at " + 221 | link + 222 | "\nIt can happen when steps area is within range but steps way is outside, special step pattern will not be generated for this steps." 223 | ); 224 | return null; 225 | } 226 | if (theFirstIntersection["indexInStepsArray"] > theSecondIntersection["indexInStepsArray"]) { 227 | // ensure that steps are going up/down - TODO!!!! 228 | var swap = theFirstIntersection; 229 | theFirstIntersection = theSecondIntersection; 230 | theSecondIntersection = swap; 231 | } 232 | return [theFirstIntersection, theSecondIntersection]; 233 | } 234 | 235 | function buildAreasSplittingStepAreaIntoSymbolicStepsFromProvidedSkeletonLines(firstLineStart, firstLineEnd, secondLineStart, secondLineEnd, pointBetweenStarts, pointBetweenEnds) { 236 | // gets lines data - one for each side of steps 237 | // firstLineStart, firstLineEnd 238 | // secondLineStart, secondLineEnd 239 | // gets data about extra geometry parts at upper and lower steps boundary 240 | // pointBetweenStarts, pointBetweenEnds 241 | // 242 | // returns array of features with extra shapes giving symbolic depiction of steps 243 | returned = []; 244 | // add _part_X tags 245 | const partCount = 4; 246 | var partIndex = partCount; 247 | while (partIndex--) { 248 | //TODO: what if steps attachment changes geometry? 249 | //the first and the last line should include also middle nodes... 250 | ratioOfStartForTop = (partIndex + 1) / partCount; 251 | ratioOfStartForBottom = partIndex / partCount; 252 | var cornerOnTopOfTheFirstLine = pointBetweenTwoPoints(firstLineStart, firstLineEnd, ratioOfStartForTop); 253 | var cornerOnBottomOfTheFirstLine = pointBetweenTwoPoints(firstLineStart, firstLineEnd, ratioOfStartForBottom); 254 | 255 | var cornerOnTopOfTheSecondLine = pointBetweenTwoPoints(secondLineStart, secondLineEnd, ratioOfStartForTop); 256 | var cornerOnBottomOfTheSecondLine = pointBetweenTwoPoints(secondLineStart, secondLineEnd, ratioOfStartForBottom); 257 | 258 | const coords = [cornerOnTopOfTheFirstLine, cornerOnTopOfTheSecondLine, cornerOnBottomOfTheSecondLine, cornerOnBottomOfTheFirstLine, cornerOnTopOfTheFirstLine]; 259 | const geometry = { type: "Polygon", coordinates: [coords] }; 260 | const generatedFeature = { type: "Feature", properties: { lunar_assembler_step_segment: "" + partIndex }, geometry: geometry }; 261 | returned.push(generatedFeature); 262 | 263 | //winding :( TODO, lets ignore it for now 264 | } 265 | return returned; 266 | } 267 | 268 | function pointBetweenTwoPoints(start, end, ratioOfStart) { 269 | return [start[0] * ratioOfStart + end[0] * (1 - ratioOfStart), start[1] * ratioOfStart + end[1] * (1 - ratioOfStart)]; 270 | } 271 | -------------------------------------------------------------------------------- /lunar_assembler_helpful_functions_for_map_styles_generate_symbolic_zebra_bars.js: -------------------------------------------------------------------------------- 1 | /* 2 | lunar_assembler - tool for generating SVG files from OpenStreetMap data. Available as a website. 3 | Copyright (C) 2021 Mateusz Konieczny 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Affero General Public License as 7 | published by the Free Software Foundation, under version 3 of the 8 | License only. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU Affero General Public License for more details. 14 | 15 | You should have received a copy of the GNU Affero General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | function generateZebraBarCrossings(dataGeojson, roadAreaWithCrossing) { 20 | if (roadAreaWithCrossing == undefined) { 21 | showWarning("zebra crossing bars not generated as undefined was passed as crossing geometry - please report bug if crossing is mapped here"); 22 | } 23 | if (roadAreaWithCrossing.type != "Feature") { 24 | showFatalError("roadAreaWithCrossing is of wrong type: " + roadAreaWithCrossing.type + " " + reportBugMessage()); 25 | } 26 | crossingLines = listUnifiedCrossingLines(dataGeojson); 27 | var i = crossingLines.length; 28 | while (i--) { 29 | var feature = crossingLines[i]; 30 | // startEndOfActualCrossing is necessary as sometimes footway=crossing is applied between sidewalks, including segment outside road area 31 | // also, this allows to catch unsupported cases (one footway=crossing across independent crossings or split footway=crossing line) 32 | // and invalid OpenStreetMap data (like footway=crossing shorter than actual crossing or footway=crossing outside crossings) 33 | showCrossingData(feature, roadAreaWithCrossing); 34 | var startEndOfActualCrossing = turf.lineIntersect(roadAreaWithCrossing, feature); 35 | if (startEndOfActualCrossing.features.length < 2 || startEndOfActualCrossing.features.length % 2 == 1) { 36 | complainAboutCrossingLineMismatchingCrossingArea(startEndOfActualCrossing, roadAreaWithCrossing, feature); 37 | } 38 | if (startEndOfActualCrossing.features.length < 2) { 39 | // skipping, generation impossible 40 | continue; 41 | } 42 | console.log("startEndOfActualCrossing.features.length"); 43 | console.log(startEndOfActualCrossing.features.length); 44 | var k = startEndOfActualCrossing.features.length - 1; 45 | while (k >= 0) { 46 | console.log(k); 47 | dataGeojson = generateGroupOfZebraBars( 48 | startEndOfActualCrossing.features[k - 1].geometry.coordinates, 49 | startEndOfActualCrossing.features[k].geometry.coordinates, 50 | roadAreaWithCrossing, 51 | dataGeojson 52 | ); 53 | k -= 2; 54 | } 55 | } 56 | return dataGeojson; 57 | } 58 | 59 | function generateGroupOfZebraBars(crossingLinePoint1, crossingLinePoint2, roadAreaWithCrossing, dataGeojson) { 60 | // always three strips, change later if needed 61 | // so 62 | // 1st empty space 63 | // 1st strip 64 | // 2nd empty space 65 | // 2nd strip 66 | // 3rd empty space 67 | // 3rd strip 68 | // 4th empty space 69 | // 70 | // so we need to split distance in 7 71 | var point1 = crossingLinePoint1; 72 | var point2 = crossingLinePoint2; 73 | dataGeojson.features.push(makeBarOfZebraCrossing(roadAreaWithCrossing, point1, point2, 1 / 7, 2 / 7)); 74 | dataGeojson.features.push(makeBarOfZebraCrossing(roadAreaWithCrossing, point1, point2, 3 / 7, 4 / 7)); 75 | dataGeojson.features.push(makeBarOfZebraCrossing(roadAreaWithCrossing, point1, point2, 5 / 7, 6 / 7)); 76 | return dataGeojson; 77 | } 78 | 79 | function showCrossingData(feature, roadAreaWithCrossing) { 80 | console.log(); 81 | console.log("-0--0--0--0--0--0--0--0-"); 82 | console.log("crossing line:"); 83 | console.log(feature); 84 | console.log("crossing areas:"); 85 | console.log(roadAreaWithCrossing); 86 | } 87 | 88 | function complainAboutCrossingLineMismatchingCrossingArea(startEndOfActualCrossing, roadAreaWithCrossing, feature) { 89 | const link = "https://www.openstreetmap.org/" + feature.id; 90 | showFatalError( 91 | link + 92 | " and touching crossing ways is/are unexpectedly crossing with road area not exactly two times (or even even count) but " + 93 | startEndOfActualCrossing.features.length + 94 | " times, which is unhandled.\n\n" + 95 | "Please, check is footway=crossing at correct object. If area:highway=crossing area exists - is end of crossing line attached to it?\n" + 96 | "Road area:\n" + 97 | JSON.stringify(roadAreaWithCrossing) + 98 | "\n\n" + 99 | "crossingLine:\n" + 100 | JSON.stringify(feature) + 101 | "\n\n" + 102 | reportBugMessageButGeodataMayBeWrong() 103 | ); 104 | } 105 | 106 | function listUnifiedCrossingLines(dataGeojson) { 107 | var i = dataGeojson.features.length; 108 | var crossingLines = []; 109 | while (i--) { 110 | var feature = dataGeojson.features[i]; 111 | if (feature.properties["footway"] == "crossing" && feature.properties["area:highway_generated_automatically"] != "yes") { 112 | crossingLines.push(JSON.parse(JSON.stringify(feature))); 113 | } 114 | } 115 | var oldCount = undefined; 116 | while (oldCount != crossingLines.length) { 117 | // lets imagine case 118 | // aaaa x cccc x bbbb 119 | // aaaa joins cccc 120 | // cccc joins bbbb 121 | // aaaa is not merged with bbbb 122 | // aaaa is merged with cccc 123 | // end of a single run is 124 | // aaaaaaaaa x bbbbb 125 | // 126 | // so either smarter algorithm or rerunning is needed 127 | oldCount = crossingLines.length; 128 | crossingLines = unifyCrossings(crossingLines); 129 | } 130 | return crossingLines; 131 | } 132 | 133 | function unifyCrossings(crossingLines) { 134 | // TODO - merge split crossings to prevent warnings above 135 | var i = -1; 136 | while (i + 1 < crossingLines.length) { 137 | i++; 138 | var k = i; 139 | while (k + 1 < crossingLines.length) { 140 | k++; 141 | var feature = crossingLines[i]; 142 | var possiblyMatching = crossingLines[k]; 143 | const link1 = "https://www.openstreetmap.org/" + feature.id; 144 | const link2 = "https://www.openstreetmap.org/" + possiblyMatching.id; 145 | var coordsOfCandidate = possiblyMatching.geometry.coordinates; 146 | if (isTheSameJSON(feature.geometry.coordinates[0], coordsOfCandidate[0])) { 147 | feature.geometry.coordinates.reverse(); 148 | coordsOfCandidate.shift(); 149 | feature.geometry.coordinates.push(...coordsOfCandidate); 150 | crossingLines.splice(k, 1); // remove matching element 151 | //showFatalError("merge 0-0 " + link1 + " " + link2 + " " + JSON.stringify(feature)) 152 | } else if (isTheSameJSON(feature.geometry.coordinates[0], coordsOfCandidate[coordsOfCandidate.length - 1])) { 153 | feature.geometry.coordinates.reverse(); 154 | coordsOfCandidate.reverse(); 155 | coordsOfCandidate.shift(); 156 | feature.geometry.coordinates.push(...coordsOfCandidate); 157 | crossingLines.splice(k, 1); // remove matching element 158 | //showFatalError("merge 0-last " + link1 + " " + link2 + " " + JSON.stringify(feature)) 159 | } else if (isTheSameJSON(feature.geometry.coordinates[feature.geometry.coordinates.length - 1], coordsOfCandidate[coordsOfCandidate.length - 1])) { 160 | coordsOfCandidate.reverse(); 161 | coordsOfCandidate.shift(); 162 | feature.geometry.coordinates.push(...coordsOfCandidate); 163 | crossingLines.splice(k, 1); // remove matching element 164 | //showFatalError("merge last-last " + link1 + " " + link2 + " " + JSON.stringify(feature)) 165 | } else if (isTheSameJSON(feature.geometry.coordinates[feature.geometry.coordinates.length - 1], coordsOfCandidate[0])) { 166 | coordsOfCandidate.shift(); 167 | feature.geometry.coordinates.push(...coordsOfCandidate); 168 | crossingLines.splice(k, 1); // remove matching element 169 | //showFatalError("merge last-0 " + link1 + " " + link2 + " " + JSON.stringify(feature)) 170 | } else { 171 | /* 172 | showError("================") 173 | showWarning((feature.geometry.coordinates[0] == coordsOfCandidate[0]) ) 174 | showError("-------------------") 175 | showWarning(feature.geometry.coordinates[0] == coordsOfCandidate[coordsOfCandidate.length - 1]) 176 | showWarning(feature.geometry.coordinates[0][0] == coordsOfCandidate[coordsOfCandidate.length - 1][0]) 177 | showWarning(feature.geometry.coordinates[0][1] == coordsOfCandidate[coordsOfCandidate.length - 1][1]) 178 | showError("-------------------") 179 | showWarning(feature.geometry.coordinates[feature.geometry.coordinates.length - 1] == coordsOfCandidate[coordsOfCandidate.length - 1]) 180 | showError("-------------------") 181 | showWarning(feature.geometry.coordinates[feature.geometry.coordinates.length - 1] == coordsOfCandidate[0]) 182 | showError("-------------------") 183 | showError("") 184 | showWarning(link1) 185 | showError(feature.geometry.coordinates[0]) 186 | showError(feature.geometry.coordinates[feature.geometry.coordinates.length - 1]) 187 | showError("") 188 | showWarning(link2) 189 | showError(coordsOfCandidate[0]) 190 | showError(coordsOfCandidate[coordsOfCandidate.length - 1]) 191 | showError("================") 192 | */ 193 | } 194 | } 195 | } 196 | return crossingLines; 197 | } 198 | 199 | function isTheSameJSON(pointA, pointB) { 200 | return JSON.stringify(pointA) === JSON.stringify(pointB); 201 | } 202 | 203 | function makeBarOfZebraCrossing(roadAreaWithCrossing, start, end, fractionOfCrossingForBarStart, fractionOfCrossingForBarEnd) { 204 | var bearingOfCrossing = turf.bearing(start, end); 205 | var lonDiff = end[0] - start[0]; 206 | var latDiff = end[1] - start[1]; 207 | 208 | var startOnCenterline = JSON.parse(JSON.stringify(start)); 209 | startOnCenterline[0] += lonDiff * fractionOfCrossingForBarStart; 210 | startOnCenterline[1] += latDiff * fractionOfCrossingForBarStart; 211 | 212 | var endOnCenterline = JSON.parse(JSON.stringify(start)); 213 | endOnCenterline[0] += lonDiff * fractionOfCrossingForBarEnd; 214 | endOnCenterline[1] += latDiff * fractionOfCrossingForBarEnd; 215 | 216 | var distance = 10; 217 | var options = { units: "meters" }; 218 | var offset1From = turf.destination(startOnCenterline, distance, bearingOfCrossing + 90, options); 219 | var offset1To = turf.destination(endOnCenterline, distance, bearingOfCrossing + 90, options); 220 | 221 | var offset2From = turf.destination(startOnCenterline, distance, bearingOfCrossing - 90, options); 222 | var offset2To = turf.destination(endOnCenterline, distance, bearingOfCrossing - 90, options); 223 | console.log(); 224 | console.log("bar"); 225 | var geometry_of_bar = { 226 | type: "Polygon", 227 | coordinates: [[offset1From.geometry.coordinates, offset1To.geometry.coordinates, offset2To.geometry.coordinates, offset2From.geometry.coordinates, offset1From.geometry.coordinates]], 228 | }; 229 | geometry_of_bar.coordinates = polygonClipping.intersection(geometry_of_bar.coordinates, roadAreaWithCrossing.geometry.coordinates); 230 | geometry_of_bar.type = "MultiPolygon"; 231 | console.log(geometry_of_bar); 232 | return { type: "Feature", geometry: geometry_of_bar, properties: { zebra_crossing_bar_generated_by_lunar_assembler: "yes" } }; 233 | } 234 | -------------------------------------------------------------------------------- /lunar_assembler_helpful_functions_for_map_styles_openstreetmap_tagging_knowledge.js: -------------------------------------------------------------------------------- 1 | /* 2 | lunar_assembler - tool for generating SVG files from OpenStreetMap data. Available as a website. 3 | Copyright (C) 2021 Mateusz Konieczny 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Affero General Public License as 7 | published by the Free Software Foundation, under version 3 of the 8 | License only. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU Affero General Public License for more details. 14 | 15 | You should have received a copy of the GNU Affero General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | function motorizedRoadValuesArray() { 20 | return [ 21 | "motorway", 22 | "motorway_link", 23 | "trunk", 24 | "trunk_link", 25 | "primary", 26 | "primary_link", 27 | "secondary", 28 | "secondary_link", 29 | "tertiary", 30 | "tertiary_link", 31 | "unclassified", 32 | "residential", 33 | "service", 34 | "track", 35 | "road", 36 | "busway", 37 | "raceway", 38 | "escape", 39 | "living_street", 40 | ]; 41 | } 42 | 43 | function railwayLinearValuesArray() { 44 | return [ 45 | "rail", 46 | "disused", 47 | "tram", 48 | "subway", 49 | "narrow_gauge", 50 | "light_rail", 51 | "preserved", 52 | "construction", 53 | "miniature", 54 | "monorail", 55 | ]; 56 | } 57 | 58 | function pedestrianWaysValuesArray() { 59 | return [ 60 | "footway", 61 | "path", 62 | "steps", 63 | "pedestrian", 64 | ]; 65 | } 66 | 67 | function linearGenerallyImpassableBarrierValuesArray() { 68 | // kerb intentionally skipped 69 | return ["fence", "wall", "hedge", "retaining_wall", "hedge_bank", "wire_fence", "city_wall", "guard_rail", "haha"]; 70 | } 71 | 72 | function widthsOfParkingLanes() { 73 | return { 74 | parallel: 3, 75 | diagonal: 5, 76 | perpendicular: 6.5, 77 | marked: 3, // may be also perpendicular or diagonal but that info is lost... 78 | no_parking: 0, 79 | no_stopping: 0, 80 | fire_lane: 0, 81 | no: 0, 82 | // separate - ??? 83 | }; 84 | } 85 | 86 | function isSimplePositiveInteger(str) { 87 | // https://stackoverflow.com/questions/10834796/validate-that-a-string-is-a-positive-integer 88 | // I believe that this snippet is below threshold of originality 89 | var n = Math.floor(Number(str)); 90 | return n !== Infinity && String(n) === str && n > 0; 91 | } 92 | 93 | function getDrivingLaneCount(feature) { 94 | if (feature.properties["lanes"] == undefined) { 95 | return undefined; 96 | } 97 | if (!isSimplePositiveInteger(feature.properties["lanes"])) { 98 | showError("Unexpected lane format lanes=" + feature.properties["lanes"] + " in " + JSON.stringify(feature) + reportBugMessage()); 99 | return undefined; 100 | } 101 | return Number(feature.properties["lanes"]); 102 | } 103 | 104 | function getParkingLaneWidthInLaneEquivalentForGivenSide(side, feature) { 105 | var matchingCodeToWidthInMeters = widthsOfParkingLanes(); 106 | var value = undefined; 107 | if (feature.properties["parking:lane:both"] != undefined) { 108 | value = feature.properties["parking:lane:both"]; 109 | } 110 | if (feature.properties["parking:lane:" + side] != undefined) { 111 | if (value != undefined) { 112 | showError("both parking:lane:both and parking:lane:" + side + " set for " + JSON.stringify(feature) + reportBugMessage()); 113 | } 114 | value = feature.properties["parking:lane:" + side]; 115 | } 116 | if (value == undefined) { 117 | return undefined; 118 | } 119 | if (value in matchingCodeToWidthInMeters) { 120 | return matchingCodeToWidthInMeters[value]; 121 | } else { 122 | showError("unexpected unhandled code " + value + " for " + side + " parking lane in " + JSON.stringify(feature) + reportBugMessage()); 123 | return undefined; 124 | } 125 | } 126 | 127 | function getParkingLaneWidthInLaneEquivalent(feature) { 128 | /* 129 | there is parallel, diagonal and perpendicular parking 130 | the width varies between them 131 | */ 132 | var left = getParkingLaneWidthInLaneEquivalentForGivenSide("left", feature); 133 | var right = getParkingLaneWidthInLaneEquivalentForGivenSide("right", feature); 134 | if (left == undefined || right == undefined) { 135 | if (left == undefined && right == undefined) { 136 | return undefined; 137 | } else { 138 | // assume that in such case user tagged known sides 139 | if (left == undefined) { 140 | left = 0; 141 | } 142 | if (right == undefined) { 143 | right = 0; 144 | } 145 | } 146 | } 147 | return (left + right) / 3; 148 | } 149 | 150 | function getTotalKnownLaneCount(feature) { 151 | var drivingLanes = getDrivingLaneCount(feature); 152 | var parkingLanes = getParkingLaneWidthInLaneEquivalent(feature); 153 | if (drivingLanes == undefined && parkingLanes == undefined) { 154 | return undefined; 155 | } 156 | if (drivingLanes != undefined && parkingLanes != undefined) { 157 | return drivingLanes + parkingLanes; 158 | } 159 | if (drivingLanes != undefined) { 160 | if(feature.properties['highway'] == 'service') { 161 | //do not assume that it means that no parking lanes are tagged 162 | return drivingLanes; 163 | } 164 | // assume that it means that no parking lanes are tagged 165 | return drivingLanes + 2; 166 | } 167 | if (parkingLanes != undefined) { 168 | // I assume that it will happen on minor city roads 169 | return parkingLanes + 1; 170 | } 171 | showFatalError("This should never happen, getTotalKnownLaneCount failed"); 172 | } 173 | 174 | function creditsForLaneWidthInMapStyle(automatically_generated_using_array) { 175 | automatically_generated_using_array.push({ key: "lanes", purpose: "estimating road width" }); 176 | for (const tag_key of ["parking:lane:both", "parking:lane:left", "parking:lane:right"]) { 177 | for (const [tag_value, _width_of_parking_lane] of Object.entries(widthsOfParkingLanes())) { 178 | automatically_generated_using_array.push({ key: tag_key, value: tag_value, purpose: "estimating road width" }); 179 | } 180 | } 181 | automatically_generated_using_array.push({ key: "oneway", value: "yes", purpose: "estimating road width" }); 182 | automatically_generated_using_array.push({ key: "oneway", value: "-1", purpose: "estimating road width" }); 183 | return automatically_generated_using_array; 184 | } 185 | -------------------------------------------------------------------------------- /lunar_assembler_helpful_functions_for_map_styles_unified_styling_handler.js: -------------------------------------------------------------------------------- 1 | /* 2 | lunar_assembler - tool for generating SVG files from OpenStreetMap data. Available as a website. 3 | Copyright (C) 2021 Mateusz Konieczny 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Affero General Public License as 7 | published by the Free Software Foundation, under version 3 of the 8 | License only. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU Affero General Public License for more details. 14 | 15 | You should have received a copy of the GNU Affero General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | function isMatcherMatchingFeature(match, feature) { 20 | if ("value" in match === false) { 21 | // matches any key 22 | if (match["key"] in feature.properties) { 23 | return true; 24 | } 25 | } else if (feature.properties[match["key"]] == match["value"]) { 26 | return true; 27 | } 28 | return false; 29 | } 30 | 31 | function getMatchFromUnifiedStyling(feature, property, styleRules) { 32 | var k = -1; 33 | while (k + 1 < styleRules.length) { 34 | k += 1; 35 | const rule = styleRules[k]; 36 | if (property in rule === false) { 37 | continue; 38 | } 39 | var i = rule["matches"].length; 40 | while (i--) { 41 | const match = rule["matches"][i]; 42 | if (Array.isArray(match)) { 43 | // multiple rules, all must be matched 44 | var m = match.length; 45 | var success = true; 46 | while (m--) { 47 | if (isMatcherMatchingFeature(match[m], feature) == false) { 48 | success = false; 49 | } 50 | } 51 | if (success) { 52 | return rule[property]; 53 | } 54 | } else { 55 | // single key=* or key=value match 56 | if (isMatcherMatchingFeature(match, feature)) { 57 | return rule[property]; 58 | } 59 | } 60 | } 61 | } 62 | return "none"; 63 | } 64 | 65 | function stylingSummary(rule) { 66 | var returned = ""; 67 | if ("area_color" in rule) { 68 | returned += '
'; 69 | } 70 | if ("line_color" in rule) { 71 | returned += '
'; 72 | } 73 | return returned; 74 | } 75 | 76 | function keyWithWikiLink(key) { 77 | var url = "https://wiki.openstreetmap.org/wiki/Key:" + encodeURIComponent(key); 78 | return '' + key + ""; 79 | } 80 | 81 | function valueWithWikiLink(key, value) { 82 | var url = "https://wiki.openstreetmap.org/wiki/Tag:" + encodeURIComponent(key + "=" + value); 83 | return '' + value + ""; 84 | } 85 | 86 | function linkedAndDescribedTag(key, value, description) { 87 | if (value == undefined) { 88 | return keyWithWikiLink(key) + "=* - " + description; 89 | } else { 90 | return keyWithWikiLink(key) + "=" + valueWithWikiLink(key, value) + " - " + description; 91 | } 92 | } 93 | 94 | function generateLegendEntry(key, value, rule) { 95 | return "
  • " + stylingSummary(rule) + " " + linkedAndDescribedTag(key, value, rule["description"]) + "
  • \n"; 96 | } 97 | 98 | function addLegendEntriesForDataStraightFromOpenStreetMap(rule) { 99 | returned = ""; 100 | var i = rule["matches"].length; 101 | while (i--) { 102 | const match = rule["matches"][i]; 103 | if (Array.isArray(match)) { 104 | // multiple rules, all must be matched 105 | var actualFiters = []; 106 | var m = match.length; 107 | while (m--) { 108 | if (match[m]["role"] === "supplementary_obvious_filter") { 109 | continue; 110 | } 111 | actualFiters.push(match[m]); 112 | } 113 | if (actualFiters.length != 1) { 114 | throw "unsupported to have multiple actual filters! - on " + JSON.stringify(match); 115 | } 116 | returned += generateLegendEntry(actualFiters[0]["key"], actualFiters[0]["value"], rule); 117 | } else { 118 | // single key=* or key=value match 119 | returned += generateLegendEntry(match["key"], match["value"], rule); 120 | } 121 | } 122 | return returned; 123 | } 124 | 125 | function addLegendEntriesForProcessedElements(rule) { 126 | returned = ""; 127 | returned += "
  • " + stylingSummary(rule) + " " + rule["description"] + " - this is generated using:\n"; 128 | returned += "
      "; 129 | if ("automatically_generated_using" in rule == false) { 130 | showFatalError("map style is broken! In " + JSON.stringify(rule) + " a field automatically_generated_using is missing!"); 131 | } 132 | if (rule["automatically_generated_using"] == undefined) { 133 | showFatalError("map style is broken! In " + JSON.stringify(rule) + " a field automatically_generated_using is set to undefined!"); 134 | } 135 | var length = rule["automatically_generated_using"].length; 136 | var i = -1; 137 | while (i + 1 < length) { 138 | i += 1; 139 | const match = rule["automatically_generated_using"][i]; 140 | if (Array.isArray(match)) { 141 | // multiple rules, all must be matched 142 | var actualFiters = []; 143 | var m = match.length; 144 | while (m--) { 145 | if (match[m]["role"] === "supplementary_obvious_filter") { 146 | continue; 147 | } 148 | actualFiters.push(match[m]); 149 | } 150 | if (actualFiters.length != 1) { 151 | throw "unsupported to have multiple actual filters! - on " + JSON.stringify(match); 152 | } 153 | returned += "
    • " + linkedAndDescribedTag(actualFiters[0]["key"], actualFiters[0]["value"], actualFiters[0]["purpose"]) + "
    • \n"; 154 | } else { 155 | // single key=* or key=value match 156 | returned += "
    • " + linkedAndDescribedTag(match["key"], match["value"], match["purpose"]) + "
    • \n"; 157 | } 158 | } 159 | returned += "
    \n"; 160 | returned += "
  • \n"; 161 | return returned; 162 | } 163 | 164 | // for high zoom: 165 | // generateLegend(highZoomMapStyle().unifiedStyling()) 166 | // in console 167 | function generateLegend(styleRules) { 168 | var returned = ""; 169 | returned += '

    Note: width styling is currently not shown in the legend

    '; 170 | returned += "\n"; 171 | returned += "
      \n"; 172 | var k = -1; 173 | while (k + 1 < styleRules.length) { 174 | k++; 175 | const rule = styleRules[k]; 176 | 177 | if ("automatically_generated_using" in rule) { 178 | returned += addLegendEntriesForProcessedElements(rule); 179 | } else { 180 | returned += addLegendEntriesForDataStraightFromOpenStreetMap(rule); 181 | } 182 | } 183 | returned += "
    "; 184 | return returned; 185 | } 186 | 187 | function generateTaginfoListing(styleRules) { 188 | var returned = []; 189 | var k = -1; 190 | while (k + 1 < styleRules.length) { 191 | k++; 192 | const rule = styleRules[k]; 193 | 194 | if ("automatically_generated_using" in rule) { 195 | returned.push(...addTaginfoListingForProcessedElements(rule)); 196 | } else { 197 | returned.push(...addTaginfoListingForDataStraightFromOpenStreetMap(rule)); 198 | } 199 | } 200 | return returned; 201 | } 202 | 203 | function addTaginfoListingForDataStraightFromOpenStreetMap(rule) { 204 | returned = []; 205 | var i = rule["matches"].length; 206 | while (i--) { 207 | var match = null; 208 | if (Array.isArray(rule["matches"][i])) { 209 | match = rule["matches"][i]; 210 | } else { 211 | match = [rule["matches"][i]]; 212 | } 213 | // multiple rules, all must be matched 214 | var m = match.length; 215 | while (m--) { 216 | if (match[m]["role"] === "supplementary_obvious_filter") { 217 | continue; 218 | } 219 | var pushed = { key: match[m]["key"], value: match[m]["value"], description: rule["description"] }; 220 | if ("value" in match[m] == false) { 221 | delete pushed["value"]; 222 | } 223 | returned.push(pushed); 224 | } 225 | } 226 | return returned; 227 | } 228 | 229 | function addTaginfoListingForProcessedElements(rule) { 230 | returned = []; 231 | var length = rule["automatically_generated_using"].length; 232 | var i = -1; 233 | while (i + 1 < length) { 234 | i += 1; 235 | var match = null; 236 | if (Array.isArray(match)) { 237 | match = rule["automatically_generated_using"][i]; 238 | } else { 239 | match = [rule["automatically_generated_using"][i]]; 240 | } 241 | var m = match.length; 242 | while (m--) { 243 | if (match[m]["role"] === "supplementary_obvious_filter") { 244 | continue; 245 | } 246 | var pushed = { key: match[m]["key"], value: match[m]["value"], description: match[m]["purpose"] }; 247 | if ("value" in match[m] == false) { 248 | delete pushed["value"]; 249 | } 250 | returned.push(pushed); 251 | } 252 | } 253 | return returned; 254 | } 255 | -------------------------------------------------------------------------------- /npm_replacement.py: -------------------------------------------------------------------------------- 1 | # Before you start making fun of it please see 2 | # https://github.com/matkoniecz/lunar_assembler/blob/master/ARCHITECTURE.md#npm-rant 3 | # 4 | # If you are aware of any project that is 5 | # - working 6 | # - using TypeScript 7 | # - using leaflet and leaflet-draw 8 | # please, please send it to https://github.com/matkoniecz/lunar_assembler/issues/new 9 | # or matkoniecz@tutanota.com 10 | # Because my attempt was a time sink leading to nothing: 11 | # https://github.com/matkoniecz/lunar_assembler/commits/npm_attempt 12 | # 13 | # The same if you are aware of any complete explanation how can I properly 14 | # manage .js dependencies and build unified .js file for distribution of my 15 | # library code with its dependencies (or some other alterbative sane way 16 | # to achieve that) 17 | 18 | 19 | import os 20 | 21 | # necessary due to 22 | # https://stackoverflow.com/questions/69991455/get-location-of-the-py-source-file-within-script-itself-also-after-os-chdir 23 | # https://gist.github.com/matkoniecz/622181cf9230af9cb80b35ae93acc1b5 24 | global build_script_location 25 | 26 | def main(): 27 | global build_script_location 28 | build_script_location = os.path.abspath(os.path.dirname(__file__)) 29 | build_distribution_form_of_library() 30 | generate_taginfo_files() 31 | run_tests() 32 | 33 | def generate_taginfo_files(): 34 | os.chdir(os.path.join(build_script_location, "examples")) 35 | os.system("node taginfo_file_generate.js") 36 | 37 | 38 | def build_distribution_form_of_library(): 39 | os.chdir(build_script_location) 40 | dependency_folder_location = os.path.join(build_script_location, "lunar_assembler_dependencies") 41 | 42 | paths_for_merging = [] 43 | paths_for_merging.append("lunar_assembler_helpful_functions_for_map_styles.js") 44 | paths_for_merging.append("lunar_assembler_helpful_functions_for_map_styles_generate_symbolic_steps_from_area_highway.js") 45 | paths_for_merging.append("lunar_assembler_helpful_functions_for_map_styles_generate_inaccessible_areas.js") 46 | paths_for_merging.append("lunar_assembler_helpful_functions_for_map_styles_generate_symbolic_zebra_bars.js") 47 | paths_for_merging.append("lunar_assembler_helpful_functions_for_map_styles_apply_patterns_to_areas.js") 48 | paths_for_merging.append("lunar_assembler_helpful_functions_for_map_styles_openstreetmap_tagging_knowledge.js") 49 | paths_for_merging.append("lunar_assembler_helpful_functions_for_map_styles_unified_styling_handler.js") 50 | paths_for_merging.append("lunar_assembler.js") 51 | paths_for_merging += path_of_files_from_folder(dependency_folder_location) 52 | 53 | output = os.path.join(build_script_location, 'examples', 'lunar_assembler.dist.js') 54 | concatenate_matching(build_script_location, paths_for_merging, output, ".js") 55 | 56 | output = os.path.join(build_script_location, 'examples', 'lunar_assembler.dist.css') 57 | concatenate_matching(build_script_location, paths_for_merging, output, ".css") 58 | 59 | for filepath in paths_for_merging: 60 | if filepath.endswith(".js"): 61 | #print("JS:", paths) 62 | pass # merged in previous step 63 | elif filepath.endswith(".css"): 64 | #print("CSS:", paths) 65 | pass # merged in previous step 66 | elif filepath.endswith("COPYING"): 67 | pass 68 | else: 69 | raise ValueError("unexpected filename " + filename) 70 | 71 | def path_of_files_from_folder(folder): 72 | returned = [] 73 | for (dirpath, dirnames, filenames) in os.walk(folder): 74 | for file in filenames: 75 | returned.append(os.path.join(folder, file)) 76 | break # without going into inner folders 77 | return returned 78 | 79 | def concatenate_matching(root_filepath, paths_for_merging, output, matcher): 80 | with open(output, 'w') as outfile: 81 | outfile.write("/* note that it is compilation of several codebases, released in total under AGPL-3.0-only, but parts are with far more liberal licenses */\n\n") 82 | for filepath in paths_for_merging: 83 | if filepath.endswith(matcher): 84 | outfile.write("\n\n" + "/* ------------------------ */" + "\n\n" + "/*" + filepath.replace(root_filepath, "") + "*/" + "\n\n") 85 | with open(filepath) as infile: 86 | code_for_merging = infile.read() 87 | code_for_merging = code_for_merging.replace("//# sourceMappingURL=leaflet.js.map", "") 88 | if "sourceMappingURL" in code_for_merging: 89 | raise ValueError("not cleaned sourceMappingURL! " + filename) 90 | outfile.write(code_for_merging) 91 | 92 | def run_tests(): 93 | print(__file__) 94 | print(os.path.dirname(__file__)) 95 | print(build_script_location) 96 | 97 | print(os.path.dirname(os.path.abspath(__file__))) 98 | 99 | from pathlib import Path 100 | 101 | source_path = Path(__file__).resolve() 102 | source_dir = source_path.parent 103 | print(source_path) 104 | print(source_dir) 105 | 106 | os.chdir(os.path.join(build_script_location, "tests")) 107 | os.system("node run_tests.js") 108 | 109 | main() -------------------------------------------------------------------------------- /tests/run_tests.js: -------------------------------------------------------------------------------- 1 | const { debug } = require("console"); 2 | var fs = require("fs"); 3 | 4 | // yes it is hack - if you are aware about a better way let me know 5 | // note that files getting imported must continue working in browser 6 | // change must not require massive restructuring of existing code 7 | eval(fs.readFileSync("../lunar_assembler_helpful_functions_for_map_styles_openstreetmap_tagging_knowledge.js") + ""); 8 | 9 | console.assert(7 == getTotalKnownLaneCount({ properties: { lanes: "5" } }), "lane tag parsing, assume presence of extra lanes"); 10 | console.assert(1 == getTotalKnownLaneCount({ properties: { lanes: "1", 'highway': 'service' } }), "lane tag parsing, do not assume presence of extra lanes for highway=service"); 11 | console.assert(5 == getTotalKnownLaneCount({ properties: { lanes: "5", "parking:lane:both": "no" } }), "lane tag parsing"); 12 | console.assert(3 == getTotalKnownLaneCount({ properties: { lanes: "1", highway: "tertiary" } }), "lane tag parsing, assume presence of parking lanes"); 13 | 14 | eval(fs.readFileSync("../lunar_assembler_helpful_functions_for_map_styles_generate_inaccessible_areas.js") + ""); 15 | 16 | var path = { properties: { highway: "path", bicycle: "designated", foot: "designated" } }; 17 | console.assert(isFeatureMakingFreePedestrianMovementPossible(path), "foot-cycle path is passable"); 18 | // console.assert(5 == 1, "message"); 19 | --------------------------------------------------------------------------------