├── .editorconfig ├── .gitignore ├── CONTRIBUTING.md ├── INSTALL.md ├── LICENSE ├── README.md ├── STRUCTURE.md ├── docs └── chromajs.png ├── expire.lua ├── external-data.yml ├── scripts └── get-external-data.py ├── serve.py ├── shortbread.lua ├── shortbread.yaml ├── shortbread ├── boundaries.sql.jinja2 ├── boundary_labels.sql.jinja2 ├── buildings.sql.jinja2 ├── dam_lines.sql.jinja2 ├── dam_polygons.sql.jinja2 ├── land.sql.jinja2 ├── ocean.sql.jinja2 ├── public_transport.sql.jinja2 ├── sites.sql.jinja2 ├── street_labels.sql.jinja2 ├── street_labels_points.sql.jinja2 ├── street_polygons.sql.jinja2 ├── streets.sql.jinja2 ├── streets_polygons_labels.sql.jinja2 ├── water_polygons.sql.jinja2 └── water_polygons_labels.sql.jinja2 ├── shortbread_original ├── addresses.14-14.sql.jinja2 ├── aerialways.12-14.sql.jinja2 ├── bridges.12-14.sql.jinja2 ├── ferries.10-14.sql.jinja2 ├── pier_lines.12-14.sql.jinja2 ├── pier_polygons.12-14.sql.jinja2 ├── place_labels.04-14.sql.jinja2 ├── pois.14-14.sql.jinja2 ├── water_lines.09-14.sql.jinja2 └── water_lines_labels.12-14.sql.jinja2 ├── spirit.lua ├── spirit.yaml ├── spirit ├── admin-names.sql.jinja2 ├── admin.sql.jinja2 ├── aeroways.sql.jinja2 ├── building-names.sql.jinja2 ├── buildings.sql.jinja2 ├── education-names.sql.jinja2 ├── education.sql.jinja2 ├── food.sql.jinja2 ├── landuse-names.sql.jinja2 ├── landuse.sql.jinja2 ├── leisure-names.sql.jinja2 ├── leisure.sql.jinja2 ├── railways-high.sql.jinja2 ├── roads-high.sql.jinja2 ├── settlements.sql.jinja2 ├── transit-points.sql.jinja2 ├── vegetation-names.sql.jinja2 ├── vegetation.sql.jinja2 ├── water-lines.sql.jinja2 ├── water-names.sql.jinja2 └── water.sql.jinja2 ├── sprites ├── airport.svg ├── bus_station.svg ├── bus_stop.svg ├── food.svg ├── subway.svg ├── train.svg ├── wetland.md └── wetland.svg ├── style.yaml ├── style ├── admin-2.yaml ├── admin-4.yaml ├── admin-6.yaml ├── admin-8.yaml ├── admin-background-dashed.yaml ├── admin-background.yaml ├── admin-names.yaml ├── aeroway-text.yaml ├── aeroways.yaml ├── background.yaml ├── building-fill.yaml ├── building-names.yaml ├── building-outline.yaml ├── education-names.yaml ├── education-outline.yaml ├── education.yaml ├── food.yaml ├── inc │ ├── regular-font.yaml │ ├── road-casing-color.yaml │ └── road-fill-color.yaml ├── landuse-names.yaml ├── landuse.yaml ├── leisure-names.yaml ├── leisure.yaml ├── rail-bridge-casing.yaml ├── rail-bridge-fill.yaml ├── rail-bridge-outer-casing.yaml ├── rail-casing.yaml ├── rail-fill.yaml ├── road-base-casing.yaml ├── road-base-fill.yaml ├── road-bridge-casing.yaml ├── road-bridge-fill.yaml ├── road-casing.yaml ├── road-fill.yaml ├── road-text.yaml ├── road-tunnel-casing.yaml ├── settlement-names.yaml ├── transit-points.yaml ├── vegetation-names.yaml ├── vegetation-pattern.yaml ├── vegetation.yaml ├── water-line-text.yaml ├── water-lines.yaml ├── water-names.yaml └── water.yaml └── themes ├── shortbread ├── init.lua └── topics │ ├── addresses.lua │ ├── aerialways.lua │ ├── boundaries.lua │ ├── boundary_labels.lua │ ├── bridges.lua │ ├── dams.lua │ ├── ferries.lua │ ├── land.lua │ ├── piers.lua │ ├── places.lua │ ├── pois.lua │ ├── public_transport.lua │ ├── sites.lua │ ├── streets.lua │ └── water.lua └── spirit ├── common.lua ├── init.lua └── topics ├── admin.lua ├── aeroway.lua ├── buildings.lua ├── education.lua ├── food.lua ├── landuse.lua ├── leisure.lua ├── places.lua ├── railway.lua ├── roads.lua ├── transit.lua ├── vegetation.lua └── water.lua /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://EditorConfig.org 2 | 3 | root = true 4 | 5 | # Defaults apply to all files 6 | [*] 7 | charset = utf-8 8 | end_of_line = lf 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | indent_style = space 12 | indent_size = 4 13 | 14 | [*.{sql.jinja2,yaml}] 15 | indent_size = 2 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | *.xml 3 | *.osm.pbf 4 | spirit.json 5 | sprites.png 6 | sprites.json 7 | sprites@2x.png 8 | sprites@2x.json 9 | z*.txt 10 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Street Spirit contribution guidelines 2 | 3 | ## Cartography 4 | 5 | ### Purpose 6 | 7 | Street Spirit aims to be a general purpose map style for OpenStreetMap data, suitable 8 | - for use as a locator map, 9 | - to show off what can be done with OpenStreetMap data, 10 | - to be up-to-date with the latest OpenStreetMap data, and 11 | - for using to orient a viewer to a location they are at. 12 | 13 | There is no ranking of these goals, and they may require compromises between the different goals. 14 | 15 | It does not seek to 16 | - be a suitable style for overlaying complex data on, 17 | - drive OpenStreetMap tagging practices, or 18 | - be a replacement for maps with a specialized topic. 19 | 20 | ### Cartographic guidelines 21 | 22 | As this style doesn't aim to have data overlayed on it, it can use all the available cartographic space. It does not need to avoid particular colours or symbols, except for icons that look like typical locators pins, as these might be used by locator maps, and would be confusing regardless. 23 | 24 | This enables a wider range of colour and saturation than typical general-purpose web maps, more similar to topographic maps and atlases. 25 | 26 | ### Technical targets 27 | 28 | We target Maplibre GL JS usage as part of a web-page that is either focused on the map, or has the map as part of a larger page. Usage across desktops, tablets, and phones is supported, with support for high-DPI displays. Smart watches and print are not targeted. 29 | 30 | ## Icons 31 | 32 | - All icons must be SVG, saved as standards compliant SVG without any proprietary tags. In Inkscape software, you will need to "Save As..." and choose the format Optimized SVG (preferable) or Plain SVG. 33 | - Use a common canvas size, which is usually 15x15 px. 34 | - Align vectors to the pixel grid. 35 | - Make a clean design, so reduced complexity where possible. 36 | 37 | ## Colours 38 | 39 | Work out colours in the LCH colour space, because RGB and other non-perceptual colourspaces are hard to reason about. 40 | 41 | The [chroma.js documentation](https://gka.github.io/chroma.js/) can function as a colour calculator. 42 | 43 | ![](docs/chromajs.png) 44 | 45 | ### Hues 46 | 47 | Use a consistent hue angle for a class of features. The following hue angles are used in this style 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 |
FeaturesHue
Water240°
Transit280°
Admin borders330°
Buildings100°
Food70°
79 | 80 | ## Required technical knowledge 81 | 82 | Contributing to the style requires some technical knowledge in the following areas 83 | 84 | - [MapLibre GL style specification](https://maplibre.org/maplibre-gl-js-docs/style-spec/), focusing on layers and expressions, including data-driven expressions; 85 | - YAML, in particular appropriate indentation for arrays. MapLibre GL styles tend to feature deeply nested arrays; and 86 | - SQL for writing read-only PostGIS queries if modifying vector tiles. 87 | -------------------------------------------------------------------------------- /INSTALL.md: -------------------------------------------------------------------------------- 1 | # Install 2 | 3 | *These instructions are for installing the **shortbread** code, not street spirit itself.* 4 | 5 | ## Requirements 6 | 7 | - [osm2pgsql](https://osm2pgsql.org/) 1.9.2+ 8 | - [osm2pgsql themepark](https://osm2pgsql.org/themepark/) 9 | - [Python](https://www.python.org/) 3.10+ 10 | - [PostgreSQL](https://www.postgresql.org/) 10+ 11 | - [PostGIS](https://postgis.net/) 3.3+ 12 | - [Tilekiln](https://github.com/pnorman/tilekiln) 0.3.0+ 13 | 14 | ## Installing dependencies 15 | 16 | Install tilekiln into a Python virtualenv with 17 | 18 | ```sh 19 | python3 -m venv venv 20 | venv/bin/pip install tilekiln 21 | ``` 22 | 23 | ### Themepark 24 | 25 | Install themepark to somewhere on your system, e.g. `$HOME/osm2pgsql-themepark` 26 | 27 | ```sh 28 | git clone https://github.com/osm2pgsql-dev/osm2pgsql-themepark.git $HOME/osm2pgsql-themepark 29 | ``` 30 | 31 | Set your LUA_PATH to include themepark, e.g. 32 | 33 | ```sh 34 | export LUA_PATH="$HOME/osm2pgsql-themepark/lua/?.lua;;" 35 | ``` 36 | ## Loading Data 37 | 38 | Data can be loaded to create tiles for Street Spirit, Shortbread, or both. 39 | 40 | ### OpenStreetMap Data 41 | 42 | You need OpenStreetMap data loaded into a PostGIS database. These stylesheets expect a database generated with osm2pgsql using the flex backend with the supplied Lua scripts. 43 | 44 | Start by creating a database and enabling PostGIS 45 | 46 | ``` 47 | sudo -u postgres createuser -s $USER 48 | createdb spirit 49 | psql -d spirit -c 'CREATE EXTENSION postgis;' 50 | ``` 51 | 52 | Grab some OpenStreetMap data. It's probably easiest to grab a PBF of OSM data from [Geofabrik](https://download.geofabrik.de/). Once you've done that, import with osm2pgsql: 53 | 54 | ``` 55 | osm2pgsql --output flex --style shortbread.lua -d spirit ~/path/to/data.osm.pbf 56 | ``` 57 | 58 | If you are only creating Shortbread tiles, instead of ``--style shortbread.lua`` use ``--style spirit.lua`` 59 | 60 | ### Scripted download 61 | Some features are rendered using preprocessed shapefiles. 62 | 63 | To download them and import them into the database you can run the following script 64 | 65 | ``` 66 | scripts/get-external-data.py 67 | ``` 68 | 69 | The script downloads shapefiles, loads them into the database and sets up the tables for rendering. Additional script option documentation can be seen with `scripts/get-external-data.py --help`. 70 | 71 | ## Serving tiles 72 | 73 | Once tilekiln is installed, run it in development mode with 74 | 75 | ```sh 76 | venv/bin/tilekiln serve dev --config shortbread.yaml --source-dbname spirit 77 | ``` 78 | 79 | To create shortbread tiles, instead use ``--config spirit.yaml``. If only creating shortbread tiles you do not need to serve sprites or view the style. 80 | 81 | 82 | ## Street spirit notes 83 | 84 | *You only need these if not generating shortbread* 85 | 86 | - [charites](https://github.com/unvt/charites) 87 | - [@basemaps/sprites](https://www.npmjs.com/package/@basemaps/sprites) 88 | 89 | 90 | Install charites with 91 | 92 | ```sh 93 | npm install @unvt/charites @basemaps/sprites 94 | ``` 95 | 96 | ## Serving sprites 97 | 98 | In another terminal window, run 99 | 100 | ```sh 101 | node_modules/.bin/basemaps-sprites sprites && python3 serve.py 102 | ``` 103 | 104 | ## Viewing the style 105 | 106 | With tiles being served in one terminal session, run a local style server with 107 | 108 | ```sh 109 | node_modules/.bin/charites serve style.yaml 110 | ``` 111 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Creative Commons Legal Code 2 | 3 | CC0 1.0 Universal 4 | 5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE 6 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN 7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS 8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES 9 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS 10 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM 11 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED 12 | HEREUNDER. 13 | 14 | Statement of Purpose 15 | 16 | The laws of most jurisdictions throughout the world automatically confer 17 | exclusive Copyright and Related Rights (defined below) upon the creator 18 | and subsequent owner(s) (each and all, an "owner") of an original work of 19 | authorship and/or a database (each, a "Work"). 20 | 21 | Certain owners wish to permanently relinquish those rights to a Work for 22 | the purpose of contributing to a commons of creative, cultural and 23 | scientific works ("Commons") that the public can reliably and without fear 24 | of later claims of infringement build upon, modify, incorporate in other 25 | works, reuse and redistribute as freely as possible in any form whatsoever 26 | and for any purposes, including without limitation commercial purposes. 27 | These owners may contribute to the Commons to promote the ideal of a free 28 | culture and the further production of creative, cultural and scientific 29 | works, or to gain reputation or greater distribution for their Work in 30 | part through the use and efforts of others. 31 | 32 | For these and/or other purposes and motivations, and without any 33 | expectation of additional consideration or compensation, the person 34 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she 35 | is an owner of Copyright and Related Rights in the Work, voluntarily 36 | elects to apply CC0 to the Work and publicly distribute the Work under its 37 | terms, with knowledge of his or her Copyright and Related Rights in the 38 | Work and the meaning and intended legal effect of CC0 on those rights. 39 | 40 | 1. Copyright and Related Rights. A Work made available under CC0 may be 41 | protected by copyright and related or neighboring rights ("Copyright and 42 | Related Rights"). Copyright and Related Rights include, but are not 43 | limited to, the following: 44 | 45 | i. the right to reproduce, adapt, distribute, perform, display, 46 | communicate, and translate a Work; 47 | ii. moral rights retained by the original author(s) and/or performer(s); 48 | iii. publicity and privacy rights pertaining to a person's image or 49 | likeness depicted in a Work; 50 | iv. rights protecting against unfair competition in regards to a Work, 51 | subject to the limitations in paragraph 4(a), below; 52 | v. rights protecting the extraction, dissemination, use and reuse of data 53 | in a Work; 54 | vi. database rights (such as those arising under Directive 96/9/EC of the 55 | European Parliament and of the Council of 11 March 1996 on the legal 56 | protection of databases, and under any national implementation 57 | thereof, including any amended or successor version of such 58 | directive); and 59 | vii. other similar, equivalent or corresponding rights throughout the 60 | world based on applicable law or treaty, and any national 61 | implementations thereof. 62 | 63 | 2. Waiver. To the greatest extent permitted by, but not in contravention 64 | of, applicable law, Affirmer hereby overtly, fully, permanently, 65 | irrevocably and unconditionally waives, abandons, and surrenders all of 66 | Affirmer's Copyright and Related Rights and associated claims and causes 67 | of action, whether now known or unknown (including existing as well as 68 | future claims and causes of action), in the Work (i) in all territories 69 | worldwide, (ii) for the maximum duration provided by applicable law or 70 | treaty (including future time extensions), (iii) in any current or future 71 | medium and for any number of copies, and (iv) for any purpose whatsoever, 72 | including without limitation commercial, advertising or promotional 73 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each 74 | member of the public at large and to the detriment of Affirmer's heirs and 75 | successors, fully intending that such Waiver shall not be subject to 76 | revocation, rescission, cancellation, termination, or any other legal or 77 | equitable action to disrupt the quiet enjoyment of the Work by the public 78 | as contemplated by Affirmer's express Statement of Purpose. 79 | 80 | 3. Public License Fallback. Should any part of the Waiver for any reason 81 | be judged legally invalid or ineffective under applicable law, then the 82 | Waiver shall be preserved to the maximum extent permitted taking into 83 | account Affirmer's express Statement of Purpose. In addition, to the 84 | extent the Waiver is so judged Affirmer hereby grants to each affected 85 | person a royalty-free, non transferable, non sublicensable, non exclusive, 86 | irrevocable and unconditional license to exercise Affirmer's Copyright and 87 | Related Rights in the Work (i) in all territories worldwide, (ii) for the 88 | maximum duration provided by applicable law or treaty (including future 89 | time extensions), (iii) in any current or future medium and for any number 90 | of copies, and (iv) for any purpose whatsoever, including without 91 | limitation commercial, advertising or promotional purposes (the 92 | "License"). The License shall be deemed effective as of the date CC0 was 93 | applied by Affirmer to the Work. Should any part of the License for any 94 | reason be judged legally invalid or ineffective under applicable law, such 95 | partial invalidity or ineffectiveness shall not invalidate the remainder 96 | of the License, and in such case Affirmer hereby affirms that he or she 97 | will not (i) exercise any of his or her remaining Copyright and Related 98 | Rights in the Work or (ii) assert any associated claims and causes of 99 | action with respect to the Work, in either case contrary to Affirmer's 100 | express Statement of Purpose. 101 | 102 | 4. Limitations and Disclaimers. 103 | 104 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 105 | surrendered, licensed or otherwise affected by this document. 106 | b. Affirmer offers the Work as-is and makes no representations or 107 | warranties of any kind concerning the Work, express, implied, 108 | statutory or otherwise, including without limitation warranties of 109 | title, merchantability, fitness for a particular purpose, non 110 | infringement, or the absence of latent or other defects, accuracy, or 111 | the present or absence of errors, whether or not discoverable, all to 112 | the greatest extent permissible under applicable law. 113 | c. Affirmer disclaims responsibility for clearing rights of other persons 114 | that may apply to the Work or any use thereof, including without 115 | limitation any person's Copyright and Related Rights in the Work. 116 | Further, Affirmer disclaims responsibility for obtaining any necessary 117 | consents, permissions or other rights required for any use of the 118 | Work. 119 | d. Affirmer understands and acknowledges that Creative Commons is not a 120 | party to this document and has no duty or obligation with respect to 121 | this CC0 or use of the Work. 122 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Street Spirit style 2 | 3 | ## About 4 | 5 | Street Spirit aims to be a general purpose map style for OpenStreetMap data, using client-side rendering and vector tiles. It is starting from a mostly fresh start, taking advantage of the decade of lessons in designing an open source stylesheet in [OpenStreetMap Carto](https://github.com/gravitystorm/openstreetmap-carto). 6 | 7 | In addition to the Street Spirit style, this code can optionally generate [Shortbread](https://shortbread-tiles.org/) schema vector tiles. 8 | 9 | ## Installing 10 | 11 | Installation instructions are in [INSTALL.md](INSTALL.md). 12 | 13 | ## Contributing 14 | 15 | Contributions to this project are welcome, see [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines. The project is currently in a state of flux, so it's best to ask before starting work. 16 | 17 | ## License 18 | 19 | ### Style and documentation 20 | 21 | Copyright Paul Norman 2022-2025 22 | 23 | This map style, map icons, and associated documentation files (the "Style") is 24 | released under the CC0 Public Domain Dedication, version 1.0, as 25 | published by Creative Commons. To the extent possible under law, the 26 | author(s) have dedicated all copyright and related and neighboring 27 | rights to the Style to the public domain worldwide. The Style is 28 | distributed WITHOUT ANY WARRANTY. 29 | 30 | You should have received a copy of the CC0 legalcode along with this 31 | work. If not, see . 32 | 33 | ### Code implementation 34 | 35 | The code is licensed under the Apache License, Version 2.0 (the "License"); 36 | you may not use this file except in compliance with the License. You may obtain a copy of the License at 37 | 38 | https://www.apache.org/licenses/LICENSE-2.0 39 | 40 | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 41 | 42 | For avoidance of doubt "the code" includes the code that is compiled into a Maplibre GL style as well as all other code in this repository. 43 | -------------------------------------------------------------------------------- /STRUCTURE.md: -------------------------------------------------------------------------------- 1 | # Project Structure 2 | 3 | ## Overview 4 | 5 | The Street Spirit contains the Street Spirit style with underlying vector tiles, code for generating [Shortbread Tiles](https://shortbread-tiles.org/), and code for generating the underlying database that powers both. 6 | 7 | ## Street Spirit client-side style 8 | 9 | Street Spirit has a client-side Maplibre GL style. The root file for this is [`style.yaml`](/style.yaml) which relies on the included files in [`/style/`](/style/). These files are built with [Charites](https://unvt.github.io/charites/) to produce the Maplibre style. 10 | 11 | ## Street Spirit vector tiles 12 | 13 | The client-side style is applied to vector tiles which are generated by [Tilekiln](https://github.com/pnorman/tilekiln) with the configuration file [`spirit.yaml`](/spirit.yaml). Layers are generated using the files in [`spirit/`](/spirit/). 14 | 15 | ## Shortbread vector tiles 16 | 17 | Shortbread tiles implement the [Shortbread tile schema](https://shortbread-tiles.org/), a basic, lean, general-purpose vector tile schema for OpenStreetMap data. Unlike the vector tiles used internally by Street Spirit, these tiles are written with the goal of being usable by many styles. The Tilekiln configuration is [`shortbread.yaml`](/shortbread.yaml) with layer files in [`shortbread/`](/shortbread/) and [`shortbread_original/`](/shortbread_original/). These files are based on ones generated by osm2pgsql-themepark and the files in `shortbread_original/` have not yet been modified. 18 | 19 | ## Database loading 20 | 21 | The database for both Street Spirit and Shortbread tiles is loaded with osm2pgsql, with utility scripts for other data sources like oceans. The lua transforms are [`spirit.lua`](/spirit.lua) and [`shortbread.lua`](/shortbread.lua). They rely on osm2pgsql themepark themes in [`themes/`](/themes/) and some themes in osm2pgsql-themepark itself. Some themes are common between the Street Spirit and Shortbread databases. 22 | 23 | In the future there will be a lua transform which combines the Street Spirit and Shortbread ones, producing a database that can be used for either type of tiles. 24 | 25 | Ocean polygons are loaded by [`scripts/get-external-data.py`](/scripts/get-external-data.py) as specified in [`external-data.yml`](/external-data.yml). 26 | -------------------------------------------------------------------------------- /docs/chromajs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pnorman/spirit/033a4117ec3604e28824b3f3608f5d163ef1b450/docs/chromajs.png -------------------------------------------------------------------------------- /expire.lua: -------------------------------------------------------------------------------- 1 | local expire = {} 2 | 3 | local function shortbread(minzoom, maxzoom, layer, mode) 4 | -- osm2pgsql requies a minzoom of at least 1 5 | if minzoom == 0 then 6 | minzoom = 1 7 | end 8 | expire[layer] = {} 9 | for zoom=minzoom, maxzoom do 10 | table.insert(expire[layer], {output=osm2pgsql.define_expire_output({ 11 | maxzoom = zoom, 12 | filename = "z"..zoom.."-"..layer..".txt", 13 | mode = mode 14 | })}) 15 | end 16 | return expire[layer] 17 | end 18 | 19 | return {shortbread = shortbread} 20 | -------------------------------------------------------------------------------- /external-data.yml: -------------------------------------------------------------------------------- 1 | settings: 2 | temp_schema: loading 3 | schema: public 4 | data_dir: data 5 | database: spirit 6 | metadata_table: external_data 7 | sources: 8 | simplified_water_polygons: 9 | # The type of file this source is 10 | type: shp 11 | # Where to get it 12 | url: https://osmdata.openstreetmap.de/download/simplified-water-polygons-split-3857.zip 13 | # The location within the archive 14 | file: simplified-water-polygons-split-3857/simplified_water_polygons.shp 15 | archive: 16 | format: zip 17 | # Files to extract from the archive 18 | files: 19 | - simplified-water-polygons-split-3857/simplified_water_polygons.cpg 20 | - simplified-water-polygons-split-3857/simplified_water_polygons.dbf 21 | - simplified-water-polygons-split-3857/simplified_water_polygons.prj 22 | - simplified-water-polygons-split-3857/simplified_water_polygons.shp 23 | - simplified-water-polygons-split-3857/simplified_water_polygons.shx 24 | water_polygons: 25 | type: shp 26 | url: https://osmdata.openstreetmap.de/download/water-polygons-split-3857.zip 27 | file: water-polygons-split-3857/water_polygons.shp 28 | archive: 29 | format: zip 30 | files: 31 | - water-polygons-split-3857/water_polygons.cpg 32 | - water-polygons-split-3857/water_polygons.dbf 33 | - water-polygons-split-3857/water_polygons.prj 34 | - water-polygons-split-3857/water_polygons.shp 35 | - water-polygons-split-3857/water_polygons.shx 36 | icesheet_polygons: 37 | type: shp 38 | url: https://osmdata.openstreetmap.de/download/antarctica-icesheet-polygons-3857.zip 39 | file: antarctica-icesheet-polygons-3857/icesheet_polygons.shp 40 | archive: 41 | format: zip 42 | files: 43 | - antarctica-icesheet-polygons-3857/icesheet_polygons.cpg 44 | - antarctica-icesheet-polygons-3857/icesheet_polygons.dbf 45 | - antarctica-icesheet-polygons-3857/icesheet_polygons.prj 46 | - antarctica-icesheet-polygons-3857/icesheet_polygons.shp 47 | - antarctica-icesheet-polygons-3857/icesheet_polygons.shx 48 | icesheet_outlines: 49 | type: shp 50 | url: https://osmdata.openstreetmap.de/download/antarctica-icesheet-outlines-3857.zip 51 | file: antarctica-icesheet-outlines-3857/icesheet_outlines.shp 52 | ogropts: 53 | - "-explodecollections" 54 | archive: 55 | format: zip 56 | files: 57 | - antarctica-icesheet-outlines-3857/icesheet_outlines.cpg 58 | - antarctica-icesheet-outlines-3857/icesheet_outlines.dbf 59 | - antarctica-icesheet-outlines-3857/icesheet_outlines.prj 60 | - antarctica-icesheet-outlines-3857/icesheet_outlines.shp 61 | - antarctica-icesheet-outlines-3857/icesheet_outlines.shx 62 | 63 | ne_110m_admin_0_boundary_lines_land: 64 | type: shp 65 | url: https://naturalearth.s3.amazonaws.com/110m_cultural/ne_110m_admin_0_boundary_lines_land.zip 66 | file: ne_110m_admin_0_boundary_lines_land.shp 67 | ogropts: &ne_opts 68 | - "--config" 69 | - "SHAPE_ENCODING" 70 | - "WINDOWS-1252" 71 | - "-explodecollections" 72 | # needs reprojecting 73 | - '-t_srs' 74 | - 'EPSG:3857' 75 | archive: 76 | format: zip 77 | files: 78 | - ne_110m_admin_0_boundary_lines_land.dbf 79 | - ne_110m_admin_0_boundary_lines_land.prj 80 | - ne_110m_admin_0_boundary_lines_land.shp 81 | - ne_110m_admin_0_boundary_lines_land.shx 82 | -------------------------------------------------------------------------------- /serve.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import http.server 3 | 4 | class CachelessHTTPRequestHandler(http.server.SimpleHTTPRequestHandler): 5 | def end_headers(self): 6 | self.send_header("Cache-Control", "no-cache, no-store, must-revalidate") 7 | self.send_header("Pragma", "no-cache") 8 | self.send_header("Expires", "0") 9 | self.send_header("Access-Control-Allow-Origin", "*") 10 | http.server.SimpleHTTPRequestHandler.end_headers(self) 11 | 12 | 13 | if __name__ == '__main__': 14 | http.server.test(HandlerClass=CachelessHTTPRequestHandler, port=8081, bind="127.0.0.1") 15 | -------------------------------------------------------------------------------- /shortbread.lua: -------------------------------------------------------------------------------- 1 | -- --------------------------------------------------------------------------- 2 | -- 3 | -- Shortbread theme 4 | -- 5 | -- Configuration for the osm2pgsql Themepark framework 6 | -- 7 | -- --------------------------------------------------------------------------- 8 | 9 | local themepark = require('themepark') 10 | 11 | themepark.debug = false 12 | 13 | script_path = debug.getinfo(1, "S").source:sub(2):match("(.*/)") 14 | if script_path ~= nil then 15 | -- osm2pgsql was told to load foo/shortbread.lua, so we need to add foo to the path 16 | package.path = script_path.."?.lua;"..package.path 17 | end 18 | 19 | -- Tell themepark where the themes are 20 | themepark:add_theme_dir('themes') 21 | 22 | themepark:add_topic('core/name-with-fallback', { 23 | keys = { 24 | name = { 'name', 'name:en', 'name:de' }, 25 | name_de = { 'name:de', 'name', 'name:en' }, 26 | name_en = { 'name:en', 'name', 'name:de' }, 27 | } 28 | }) 29 | 30 | -- -------------------------------------------------------------------------- 31 | 32 | themepark:add_topic('spirit/buildings') 33 | themepark:add_topic('spirit/roads') 34 | themepark:add_topic('spirit/railway') 35 | themepark:add_topic('spirit/aeroway') 36 | 37 | themepark:add_topic('core/layer') 38 | 39 | themepark:add_topic('shortbread/aerialways') 40 | themepark:add_topic('shortbread/boundary_labels') 41 | themepark:add_topic('shortbread/bridges') 42 | themepark:add_topic('shortbread/dams') 43 | themepark:add_topic('shortbread/ferries') 44 | themepark:add_topic('shortbread/land') 45 | themepark:add_topic('shortbread/piers') 46 | themepark:add_topic('shortbread/places') 47 | themepark:add_topic('shortbread/public_transport') 48 | themepark:add_topic('shortbread/sites') 49 | themepark:add_topic('shortbread/water') 50 | 51 | themepark:add_topic('shortbread/pois') 52 | -- Must be after "pois" layer, because as per Shortbread spec addresses that 53 | -- are already in "pois" should not be in the "addresses" layer. 54 | themepark:add_topic('shortbread/addresses') 55 | themepark:add_topic('shortbread/boundaries') 56 | themepark:add_topic('shortbread/streets') 57 | 58 | -- --------------------------------------------------------------------------- 59 | -------------------------------------------------------------------------------- /shortbread/boundaries.sql.jinja2: -------------------------------------------------------------------------------- 1 | SELECT 2 | ST_AsMVTGeom(geom, {{unbuffered_bbox}}, {{extent}}, {{buffer}}) AS way, 3 | admin_level, 4 | maritime, 5 | disputed 6 | FROM boundaries 7 | WHERE geom && {{bbox}} 8 | {% if zoom < 7 %} 9 | AND admin_level = 2 10 | {% endif %} 11 | -------------------------------------------------------------------------------- /shortbread/boundary_labels.sql.jinja2: -------------------------------------------------------------------------------- 1 | SELECT 2 | ST_AsMVTGeom(geom, {{unbuffered_bbox}}, {{extent}}, {{buffer}}) AS way, 3 | name, 4 | name_de, 5 | name_en, 6 | admin_level, 7 | way_area 8 | FROM boundary_labels 9 | WHERE geom && {{bbox}} 10 | AND {{zoom}} >= minzoom 11 | ORDER BY way_area DESC 12 | -------------------------------------------------------------------------------- /shortbread/buildings.sql.jinja2: -------------------------------------------------------------------------------- 1 | SELECT 2 | ST_Collect(merged.geom) AS way 3 | {# 4 | The order of merging is important. If you ST_AsMVTGeom(ST_Collect) you can effectively get a 5 | ST_Union in some cases. To avoid this, we first transform to the VT coords, then collect. But 6 | because a geom might be a multipolygon or st_asmvtgeom could turn a polygon into a MP (e.g. 7 | way 391482237), we have to ST_Dump the geoms to turn multis into polys. Unfortunately, we can't 8 | just ST_Collect(ST_Dump(...)) because you can't mix an aggregate and set-returning function like 9 | this. So we have to do a LATERAL join. 10 | #} 11 | FROM buildings, 12 | LATERAL ST_Dump(ST_AsMVTGeom(geom, {{unbuffered_bbox}}, {{extent}}, {{buffer}})) AS merged 13 | WHERE buildings.geom && {{bbox}} 14 | AND way_area > {{coordinate_area}} 15 | -------------------------------------------------------------------------------- /shortbread/dam_lines.sql.jinja2: -------------------------------------------------------------------------------- 1 | SELECT 2 | ST_AsMVTGeom(geom, {{unbuffered_bbox}}, {{extent}}, {{buffer}}) AS way, 3 | kind 4 | FROM dam_lines 5 | WHERE geom && {{bbox}} 6 | -------------------------------------------------------------------------------- /shortbread/dam_polygons.sql.jinja2: -------------------------------------------------------------------------------- 1 | SELECT 2 | ST_AsMVTGeom(geom, {{unbuffered_bbox}}, {{extent}}, {{buffer}}) AS way, 3 | kind 4 | FROM dam_polygons 5 | WHERE geom && {{bbox}} 6 | -------------------------------------------------------------------------------- /shortbread/land.sql.jinja2: -------------------------------------------------------------------------------- 1 | SELECT 2 | ST_AsMVTGeom(ST_SimplifyPreserveTopology(ST_Collect(geom), {{ coordinate_length }}), {{unbuffered_bbox}}, {{extent}}, {{buffer}}) AS way, 3 | kind 4 | FROM land 5 | WHERE geom && {{bbox}} 6 | AND {{zoom}} >= minzoom 7 | AND ST_Area(geom) > 4*{{ coordinate_area }} 8 | GROUP BY kind 9 | -------------------------------------------------------------------------------- /shortbread/ocean.sql.jinja2: -------------------------------------------------------------------------------- 1 | SELECT 2 | ST_AsMVTGeom(ST_SimplifyPreserveTopology(ST_Union(way), {{ coordinate_length }}), {{unbuffered_bbox}}, {{extent}}, {{buffer}}) AS way 3 | {% if zoom <= 8 %} 4 | FROM simplified_water_polygons 5 | {% else %} 6 | FROM water_polygons 7 | {% endif %} 8 | WHERE way && {{bbox}} 9 | -------------------------------------------------------------------------------- /shortbread/public_transport.sql.jinja2: -------------------------------------------------------------------------------- 1 | SELECT 2 | ST_AsMVTGeom(geom, {{unbuffered_bbox}}, {{extent}}, {{buffer}}) AS way, 3 | name, 4 | name_de, 5 | name_en, 6 | kind 7 | FROM public_transport 8 | WHERE geom && {{bbox}} 9 | AND {{zoom}} >= minzoom 10 | -------------------------------------------------------------------------------- /shortbread/sites.sql.jinja2: -------------------------------------------------------------------------------- 1 | SELECT 2 | ST_AsMVTGeom(geom, {{unbuffered_bbox}}, {{extent}}, {{buffer}}) AS way, 3 | kind 4 | FROM sites 5 | WHERE geom && {{bbox}} 6 | -------------------------------------------------------------------------------- /shortbread/street_labels.sql.jinja2: -------------------------------------------------------------------------------- 1 | WITH road AS ( 2 | SELECT 3 | geom, 4 | highway AS kind, 5 | name, 6 | ref, 7 | z_order 8 | FROM roads 9 | WHERE geom && {{bbox}} 10 | AND (ref IS NOT NULL OR name IS NOT NULL) 11 | AND highway IN 12 | ( 13 | {# 14 | Formatting this way is slightly awkward since the `,' only goes on lines with something following. 15 | Better would be to template this with Jinja somehow 16 | #} 17 | 'motorway' 18 | {% if zoom >=12 %}, 'trunk', 'primary'{% endif %} 19 | {% if zoom >=13 %}, 'motorway_link', 'trunk_link', 'primary_link', 'secondary', 'secondary_link', 'tertiary'{% endif %} 20 | {% if zoom >=14 %}, 'tertiary_link', 'unclassified', 'residential', 'living_street', 'busway', 'bus_guideway', 21 | 'service', 'pedestrian', 'track', 'footway', 'steps', 'path', 'cycleway'{% endif %} 22 | ) 23 | ), 24 | rail AS ( 25 | SELECT 26 | geom, 27 | railway AS kind, 28 | NULL AS name, -- TODO: add railway names 29 | ref, 30 | z_order 31 | FROM railways 32 | WHERE geom && {{bbox}} 33 | AND (ref IS NOT NULL OR name IS NOT NULL) 34 | AND railway IN ('rail', 'narrow_gauge', 'tram', 'light_rail', 'funicular', 'subway', 'monorail') 35 | ), 36 | {% if zoom >=11 %} 37 | aeroway AS ( 38 | SELECT 39 | geom, 40 | aeroway AS kind, 41 | NULL AS name, 42 | ref, 43 | CASE WHEN aeroway = 'runway' THEN 510 ELSE 500 END AS z_order 44 | FROM aeroways 45 | WHERE geom && {{bbox}} 46 | AND (ref IS NOT NULL) 47 | AND aeroway IN ('runway'{% if zoom >=13 %}, 'taxiway'{% endif %}) 48 | ), 49 | {% endif %} 50 | {% set columns = 'kind, name, ref' %} 51 | all_geoms AS ( 52 | SELECT 53 | geom, {{columns}}, z_order 54 | FROM road 55 | UNION ALL 56 | SELECT 57 | geom, {{columns}}, z_order 58 | FROM rail 59 | {% if zoom >=11 %} 60 | UNION ALL 61 | SELECT 62 | geom, {{columns}}, z_order 63 | FROM aeroway 64 | {% endif %} 65 | ), 66 | merged AS ( 67 | SELECT 68 | ST_LineMerge(ST_Collect(geom)) AS geom, 69 | kind, 70 | name, 71 | string_to_array(ref, ';') AS refs, 72 | z_order 73 | FROM all_geoms 74 | GROUP BY {{columns}}, z_order 75 | ) 76 | SELECT 77 | ST_AsMVTGeom(ST_Simplify((ST_Dump(geom)).geom, {{ coordinate_length }}), {{unbuffered_bbox}}, {{extent}}, {{buffer}}) AS way, 78 | kind, 79 | name, 80 | array_to_string(refs, E'\n') AS ref, 81 | array_length(refs,1) AS ref_rows, 82 | (SELECT MAX(char_length(ref)) FROM unnest(refs) AS u(ref)) AS ref_cols 83 | FROM merged 84 | ORDER BY z_order DESC 85 | -------------------------------------------------------------------------------- /shortbread/street_labels_points.sql.jinja2: -------------------------------------------------------------------------------- 1 | SELECT 2 | ST_AsMVTGeom(geom, {{unbuffered_bbox}}, {{extent}}, {{buffer}}) AS way, 3 | name, 4 | name_de, 5 | name_en, 6 | kind, 7 | ref 8 | FROM street_labels_points 9 | WHERE geom && {{bbox}} 10 | -------------------------------------------------------------------------------- /shortbread/street_polygons.sql.jinja2: -------------------------------------------------------------------------------- 1 | SELECT 2 | ST_AsMVTGeom(geom, {{unbuffered_bbox}}, {{extent}}, {{buffer}}) AS way, 3 | kind, 4 | rail, 5 | tunnel, 6 | bridge, 7 | surface 8 | FROM street_polygons 9 | WHERE geom && {{bbox}} 10 | {% if zoom < 13 %} 11 | AND kind IN ('runway') 12 | {% elif zoom < 14 %} 13 | AND kind IN ('runway', 'taxiway') 14 | {% else %} 15 | AND kind IN ('runway', 'taxiway', 'service', 'pedestrian') 16 | {% endif %} 17 | ORDER BY z_order DESC 18 | -------------------------------------------------------------------------------- /shortbread/streets.sql.jinja2: -------------------------------------------------------------------------------- 1 | WITH road AS ( 2 | SELECT 3 | CASE WHEN oneway = '-1' THEN ST_Reverse(geom) ELSE geom END AS geom, 4 | CASE highway 5 | WHEN 'motorway_link' THEN 'motorway' 6 | WHEN 'trunk_link' THEN 'trunk' 7 | WHEN 'primary_link' THEN 'primary' 8 | WHEN 'secondary_link' THEN 'secondary' 9 | WHEN 'tertiary_link' THEN 'tertiary' 10 | ELSE highway 11 | END AS kind, 12 | CASE WHEN highway IN ('motorway_link', 'trunk_link', 'primary_link', 'secondary_link', 'tertiary_link') THEN true END AS link, 13 | false AS rail, 14 | coalesce(tunnel, false) AS tunnel, 15 | coalesce(bridge, false) AS bridge, 16 | coalesce(oneway IN ('yes', '1', 'true', '-1'), false) AS oneway, 17 | false AS oneway_reverse, 18 | tracktype, 19 | surface, 20 | service, 21 | bicycle, 22 | horse, 23 | z_order 24 | FROM roads 25 | WHERE geom && {{bbox}} 26 | AND highway IN 27 | ( 28 | {# 29 | Formatting this way is slightly awkward since the `,' only goes on lines with something following. 30 | Better would be to template this with Jinja somehow 31 | #} 32 | 'motorway', 'motorway_link' 33 | {% if zoom >=6 %}, 'trunk', 'trunk_link'{% endif %} 34 | {% if zoom >=8 %}, 'primary', 'primary_link'{% endif %} 35 | {% if zoom >=9 %}, 'secondary', 'secondary_link'{% endif %} 36 | {% if zoom >=10 %}, 'tertiary', 'tertiary_link'{% endif %} 37 | {% if zoom >=12 %}, 'unclassified', 'residential', 'busway', 'bus_guideway'{% endif %} 38 | {% if zoom >= 13 %}, 'living_street', 'service', 'pedestrian', 'track', 'footway', 'steps', 'path', 'cycleway'{% endif %} 39 | ) 40 | ), 41 | {% if zoom >=8 %} 42 | rail AS ( 43 | SELECT 44 | geom, 45 | railway AS kind, 46 | false AS link, 47 | true AS rail, 48 | coalesce(tunnel, false) AS tunnel, 49 | coalesce(bridge, false) AS bridge, 50 | false AS oneway, 51 | false AS oneway_reverse, 52 | NULL as tracktype, 53 | service, 54 | NULL AS bicycle, 55 | NULL AS horse, 56 | z_order 57 | FROM railways 58 | WHERE geom && {{bbox}} 59 | AND railway IN 60 | ( 61 | 'rail', 'narrow_gauge' 62 | {% if zoom >=10 %}, 'tram', 'light_rail', 'funicular', 'subway', 'monorail'{% endif %} 63 | ) 64 | ), 65 | {% endif %} 66 | {% if zoom >=11 %} 67 | aeroway AS ( 68 | SELECT 69 | geom, 70 | aeroway AS kind, 71 | false AS link, 72 | true AS rail, 73 | false AS tunnel, 74 | false AS bridge, 75 | false AS oneway, 76 | false AS oneway_reverse, 77 | NULL as tracktype, 78 | NULL AS service, 79 | NULL AS bicycle, 80 | NULL AS horse, 81 | CASE WHEN aeroway = 'runway' THEN 510 ELSE 500 END AS z_order 82 | FROM aeroways 83 | WHERE geom && {{bbox}} 84 | AND aeroway IN ('runway'{% if zoom >=11 %}, 'taxiway'{% endif %}) 85 | ), 86 | {% endif %} 87 | {% if zoom >= 14 %} 88 | {% set columns = 'kind, link, rail, tunnel, bridge, oneway, oneway_reverse, tracktype, service' %} 89 | {% elif zoom >= 11 %} 90 | {% set columns = 'kind, link, rail, tunnel, bridge, tracktype, service, bicycle, horse' %} 91 | {% else %} {# zoom >= 5 #} 92 | {% set columns = 'kind, rail' %} 93 | {% endif %} 94 | 95 | all_geoms AS ( 96 | SELECT 97 | geom, {{columns}}, z_order 98 | FROM road 99 | {% if zoom >=8 %} 100 | UNION ALL 101 | SELECT 102 | geom, {{columns}}, z_order 103 | FROM rail 104 | {% endif %} 105 | {% if zoom >=11 %} 106 | UNION ALL 107 | SELECT 108 | geom, {{columns}}, z_order 109 | FROM aeroway 110 | {% endif %} 111 | ), 112 | merged AS ( 113 | SELECT 114 | {% if zoom >= 14 %} 115 | ST_LineMerge(ST_Collect(geom), oneway IS NOT DISTINCT FROM true) AS geom, 116 | {% else %} 117 | ST_LineMerge(ST_Collect(geom)) AS geom, 118 | {% endif %} 119 | {{columns}}, z_order 120 | FROM all_geoms 121 | GROUP BY {{columns}}, z_order 122 | ) 123 | SELECT 124 | ST_AsMVTGeom(ST_Simplify((ST_Dump(geom)).geom, {{ coordinate_length }}), {{unbuffered_bbox}}, {{extent}}, {{buffer}}) AS way, 125 | {{columns}} 126 | FROM merged 127 | ORDER BY z_order DESC 128 | -------------------------------------------------------------------------------- /shortbread/streets_polygons_labels.sql.jinja2: -------------------------------------------------------------------------------- 1 | SELECT 2 | ST_AsMVTGeom(geom, {{unbuffered_bbox}}, {{extent}}, {{buffer}}) AS way, 3 | name, 4 | name_de, 5 | name_en, 6 | kind 7 | FROM streets_polygons_labels 8 | WHERE geom && {{bbox}} 9 | -------------------------------------------------------------------------------- /shortbread/water_polygons.sql.jinja2: -------------------------------------------------------------------------------- 1 | SELECT 2 | ST_AsMVTGeom(geom, {{unbuffered_bbox}}, {{extent}}, {{buffer}}) AS way, 3 | kind, 4 | way_area 5 | FROM water_areas 6 | WHERE geom && {{bbox}} 7 | AND ST_Area(geom) > 4*{{ coordinate_area }} 8 | -------------------------------------------------------------------------------- /shortbread/water_polygons_labels.sql.jinja2: -------------------------------------------------------------------------------- 1 | SELECT 2 | ST_AsMVTGeom(geom, {{unbuffered_bbox}}, {{extent}}, {{buffer}}) AS way, 3 | name, 4 | name_de, 5 | name_en, 6 | kind, 7 | way_area 8 | FROM water_area_labels 9 | WHERE geom && {{bbox}} ORDER BY way_area desc 10 | {% if zoom < 14 %} 11 | LIMIT 256 12 | {% endif %} 13 | -------------------------------------------------------------------------------- /shortbread_original/addresses.14-14.sql.jinja2: -------------------------------------------------------------------------------- 1 | SELECT 2 | ST_AsMVTGeom(geom, {{unbuffered_bbox}}, {{extent}}, {{buffer}}) AS way, 3 | housename, 4 | housenumber 5 | FROM addresses 6 | WHERE geom && {{bbox}} 7 | -------------------------------------------------------------------------------- /shortbread_original/aerialways.12-14.sql.jinja2: -------------------------------------------------------------------------------- 1 | SELECT 2 | ST_AsMVTGeom(geom, {{unbuffered_bbox}}, {{extent}}, {{buffer}}) AS way, 3 | kind 4 | FROM aerialways 5 | WHERE geom && {{bbox}} 6 | -------------------------------------------------------------------------------- /shortbread_original/bridges.12-14.sql.jinja2: -------------------------------------------------------------------------------- 1 | SELECT 2 | ST_AsMVTGeom(geom, {{unbuffered_bbox}}, {{extent}}, {{buffer}}) AS way, 3 | kind 4 | FROM bridges 5 | WHERE geom && {{bbox}} 6 | -------------------------------------------------------------------------------- /shortbread_original/ferries.10-14.sql.jinja2: -------------------------------------------------------------------------------- 1 | SELECT 2 | ST_AsMVTGeom(geom, {{unbuffered_bbox}}, {{extent}}, {{buffer}}) AS way, 3 | name, 4 | name_de, 5 | name_en, 6 | kind 7 | FROM ferries 8 | WHERE geom && {{bbox}} 9 | AND {{zoom}} >= minzoom 10 | -------------------------------------------------------------------------------- /shortbread_original/pier_lines.12-14.sql.jinja2: -------------------------------------------------------------------------------- 1 | SELECT 2 | ST_AsMVTGeom(geom, {{unbuffered_bbox}}, {{extent}}, {{buffer}}) AS way, 3 | kind 4 | FROM pier_lines 5 | WHERE geom && {{bbox}} 6 | -------------------------------------------------------------------------------- /shortbread_original/pier_polygons.12-14.sql.jinja2: -------------------------------------------------------------------------------- 1 | SELECT 2 | ST_AsMVTGeom(geom, {{unbuffered_bbox}}, {{extent}}, {{buffer}}) AS way, 3 | kind 4 | FROM pier_polygons 5 | WHERE geom && {{bbox}} 6 | -------------------------------------------------------------------------------- /shortbread_original/place_labels.04-14.sql.jinja2: -------------------------------------------------------------------------------- 1 | SELECT 2 | ST_AsMVTGeom(geom, {{unbuffered_bbox}}, {{extent}}, {{buffer}}) AS way, 3 | name, 4 | name_de, 5 | name_en, 6 | kind, 7 | population 8 | FROM place_labels 9 | WHERE geom && {{bbox}} 10 | AND {{zoom}} >= minzoom 11 | ORDER BY population desc 12 | -------------------------------------------------------------------------------- /shortbread_original/pois.14-14.sql.jinja2: -------------------------------------------------------------------------------- 1 | SELECT 2 | ST_AsMVTGeom(geom, {{unbuffered_bbox}}, {{extent}}, {{buffer}}) AS way, 3 | name, 4 | name_de, 5 | name_en, 6 | amenity, 7 | leisure, 8 | tourism, 9 | shop, 10 | man_made, 11 | historic, 12 | emergency, 13 | highway, 14 | office, 15 | housename, 16 | housenumber, 17 | cuisine, 18 | sport, 19 | vending, 20 | information, 21 | "tower:type", 22 | religion, 23 | denomination, 24 | "recycling:glass_bottles", 25 | "recycling:paper", 26 | "recycling:clothes", 27 | "recycling:scrap_metal", 28 | atm 29 | FROM pois 30 | WHERE geom && {{bbox}} 31 | -------------------------------------------------------------------------------- /shortbread_original/water_lines.09-14.sql.jinja2: -------------------------------------------------------------------------------- 1 | SELECT 2 | ST_AsMVTGeom(geom, {{unbuffered_bbox}}, {{extent}}, {{buffer}}) AS way, 3 | kind, 4 | tunnel, 5 | bridge, 6 | layer 7 | FROM water_lines 8 | WHERE geom && {{bbox}} 9 | AND {{zoom}} >= minzoom 10 | ORDER BY layer ASC 11 | -------------------------------------------------------------------------------- /shortbread_original/water_lines_labels.12-14.sql.jinja2: -------------------------------------------------------------------------------- 1 | SELECT ST_AsMVTGeom(geom, {{unbuffered_bbox}}, {{extent}}, {{buffer}}) AS way, 2 | name, 3 | name_de, 4 | name_en, 5 | kind, 6 | minzoom 7 | FROM water_lines_labels 8 | WHERE geom && {{bbox}} 9 | -------------------------------------------------------------------------------- /spirit.lua: -------------------------------------------------------------------------------- 1 | -- --------------------------------------------------------------------------- 2 | -- 3 | -- Street Spirit theme 4 | -- 5 | -- Configuration for the osm2pgsql Themepark framework 6 | -- 7 | -- --------------------------------------------------------------------------- 8 | 9 | local themepark = require('themepark') 10 | 11 | themepark.debug = false 12 | 13 | script_path = debug.getinfo(1, "S").source:sub(2):match("(.*/)") 14 | if script_path ~= nil then 15 | -- osm2pgsql was told to load foo/spirit.lua, so we need to add foo to the path 16 | package.path = script_path.."?.lua;"..package.path 17 | end 18 | 19 | -- Tell themepark where the themes are 20 | themepark:add_theme_dir('themes') 21 | 22 | themepark:add_topic('spirit/buildings') 23 | themepark:add_topic('spirit/water') 24 | themepark:add_topic('spirit/education') 25 | themepark:add_topic('spirit/food') 26 | themepark:add_topic('spirit/leisure') 27 | themepark:add_topic('spirit/vegetation') 28 | themepark:add_topic('spirit/landuse') 29 | themepark:add_topic('spirit/roads') 30 | themepark:add_topic('spirit/railway') 31 | themepark:add_topic('spirit/transit') 32 | themepark:add_topic('spirit/aeroway') 33 | themepark:add_topic('spirit/places') 34 | themepark:add_topic('spirit/admin') 35 | -------------------------------------------------------------------------------- /spirit.yaml: -------------------------------------------------------------------------------- 1 | metadata: 2 | id: v1 3 | name: Street Spirit 4 | attribution: © OpenStreetMap contributors 5 | version: 0.0.1 6 | center: [0, 0, 4] 7 | vector_layers: 8 | admin: 9 | description: Administrative boundaries 10 | sql: 11 | - minzoom: 4 12 | maxzoom: 14 13 | buffer: 16 14 | extent: 1024 15 | file: spirit/admin.sql.jinja2 16 | - minzoom: 15 17 | maxzoom: 15 18 | buffer: 16 19 | extent: 4096 20 | file: spirit/admin.sql.jinja2 21 | admin-names: 22 | description: Administrative area names 23 | sql: 24 | - minzoom: 0 25 | maxzoom: 14 26 | buffer: 16 27 | extent: 512 28 | file: spirit/admin-names.sql.jinja2 29 | 30 | buildings: 31 | description: Building polygons 32 | sql: 33 | - minzoom: 13 34 | maxzoom: 14 35 | extent: 1024 36 | buffer: 2 37 | file: spirit/buildings.sql.jinja2 38 | - minzoom: 15 39 | maxzoom: 15 40 | extent: 4096 41 | buffer: 8 42 | file: spirit/buildings.sql.jinja2 43 | building-names: 44 | description: Building names 45 | sql: 46 | - minzoom: 15 47 | maxzoom: 15 48 | extent: 2048 49 | buffer: 8 50 | file: spirit/building-names.sql.jinja2 51 | water: 52 | description: Ocean and water polygons 53 | sql: 54 | - minzoom: 0 55 | maxzoom: 14 56 | extent: 1024 57 | file: spirit/water.sql.jinja2 58 | - minzoom: 15 59 | maxzoom: 15 60 | extent: 4096 61 | file: spirit/water.sql.jinja2 62 | water-lines: 63 | description: Water linestrings 64 | fields: 65 | waterway: Type of waterway 66 | name: Name of waterway 67 | sql: 68 | - minzoom: 8 69 | maxzoom: 14 70 | extent: 1024 71 | buffer: 2 72 | file: spirit/water-lines.sql.jinja2 73 | - minzoom: 15 74 | maxzoom: 15 75 | extent: 4096 76 | buffer: 8 77 | file: spirit/water-lines.sql.jinja2 78 | water-names: 79 | description: Names for water bodies 80 | fields: 81 | name: Name of water body 82 | sql: 83 | - minzoom: 8 84 | maxzoom: 14 85 | extent: 1024 86 | buffer: 2 87 | file: spirit/water-names.sql.jinja2 88 | - minzoom: 15 89 | maxzoom: 15 90 | extent: 4096 91 | buffer: 8 92 | file: spirit/water-names.sql.jinja2 93 | roads: 94 | description: Linear road features 95 | fields: 96 | highway: Type of highway 97 | sql: 98 | - minzoom: 10 99 | maxzoom: 14 100 | extent: 1024 101 | buffer: 4 102 | file: spirit/roads-high.sql.jinja2 103 | - minzoom: 15 104 | maxzoom: 15 105 | extent: 4096 106 | buffer: 16 107 | file: spirit/roads-high.sql.jinja2 108 | railways: 109 | description: Linear railway features 110 | fields: 111 | highway: Type of highway 112 | sql: 113 | - minzoom: 12 114 | maxzoom: 14 115 | extent: 1024 116 | buffer: 4 117 | file: spirit/railways-high.sql.jinja2 118 | - minzoom: 15 119 | maxzoom: 15 120 | extent: 4096 121 | buffer: 16 122 | file: spirit/railways-high.sql.jinja2 123 | aeroways: 124 | description: Runways and other aeroways 125 | fields: {} 126 | sql: 127 | - minzoom: 10 128 | maxzoom: 14 129 | extent: 1024 130 | buffer: 8 131 | file: spirit/aeroways.sql.jinja2 132 | - minzoom: 15 133 | maxzoom: 15 134 | extent: 4096 135 | buffer: 64 # Runways get wide at high zooms, so they need an extra-large buffer 136 | file: spirit/aeroways.sql.jinja2 137 | transit-points: 138 | description: Transit-oriented points 139 | fields: 140 | station: If the point is a station or stop 141 | mode: Mode of transportation 142 | sql: 143 | - minzoom: 12 144 | maxzoom: 14 145 | extent: 512 146 | buffer: 0 147 | file: spirit/transit-points.sql.jinja2 148 | - minzoom: 15 149 | maxzoom: 15 150 | extent: 2048 151 | buffer: 0 152 | file: spirit/transit-points.sql.jinja2 153 | landuse: 154 | description: Various types of landuse 155 | fields: 156 | landuse: Type of landuse 157 | sql: 158 | - minzoom: 10 159 | maxzoom: 14 160 | extent: 1024 161 | buffer: 4 162 | file: spirit/landuse.sql.jinja2 163 | - minzoom: 15 164 | maxzoom: 15 165 | extent: 4096 166 | buffer: 16 167 | file: spirit/landuse.sql.jinja2 168 | landuse-names: 169 | description: Various types of landuse 170 | fields: 171 | landuse: Type of landuse 172 | name: Name of landuse 173 | way_area: Area in square meters in web mercator 174 | sql: 175 | - minzoom: 10 176 | maxzoom: 14 177 | extent: 256 178 | buffer: 0 179 | file: spirit/landuse-names.sql.jinja2 180 | - minzoom: 15 181 | maxzoom: 15 182 | extent: 1024 183 | buffer: 0 184 | file: spirit/landuse-names.sql.jinja2 185 | education: 186 | description: Various types of educational areas 187 | fields: 188 | landuse: Type of landuse 189 | sql: 190 | - minzoom: 10 191 | maxzoom: 14 192 | extent: 1024 193 | buffer: 4 194 | file: spirit/education.sql.jinja2 195 | - minzoom: 15 196 | maxzoom: 15 197 | extent: 4096 198 | buffer: 16 199 | file: spirit/education.sql.jinja2 200 | education-names: 201 | description: Names of educational facilities 202 | fields: 203 | education: Type of landuse 204 | name: Name of education 205 | sql: 206 | - minzoom: 10 207 | maxzoom: 14 208 | extent: 256 209 | buffer: 0 210 | file: spirit/education-names.sql.jinja2 211 | - minzoom: 15 212 | maxzoom: 15 213 | extent: 1024 214 | buffer: 0 215 | file: spirit/education-names.sql.jinja2 216 | leisure: 217 | description: Parks and other leisure areas 218 | sql: 219 | - minzoom: 10 220 | maxzoom: 14 221 | extent: 1024 222 | buffer: 4 223 | file: spirit/leisure.sql.jinja2 224 | - minzoom: 15 225 | maxzoom: 15 226 | extent: 4096 227 | buffer: 16 228 | file: spirit/leisure.sql.jinja2 229 | leisure-names: 230 | description: Parks and other leisure area names 231 | fields: 232 | way_area: Area in square meters in web mercator 233 | sql: 234 | - minzoom: 10 235 | maxzoom: 14 236 | extent: 256 237 | buffer: 0 238 | file: spirit/leisure-names.sql.jinja2 239 | - minzoom: 15 240 | maxzoom: 15 241 | extent: 1024 242 | buffer: 0 243 | file: spirit/leisure-names.sql.jinja2 244 | settlements: 245 | description: Populated places 246 | fields: {} 247 | sql: 248 | sql: 249 | - minzoom: 8 250 | maxzoom: 14 251 | extent: 1024 252 | buffer: 4 253 | file: spirit/settlements.sql.jinja2 254 | - minzoom: 15 255 | maxzoom: 15 256 | extent: 4096 257 | buffer: 16 258 | file: spirit/settlements.sql.jinja2 259 | vegetation: 260 | description: vegetation areas 261 | fields: 262 | vegetation: Type of vegetation. One of wood, heath, scrub, wetland, or grass 263 | wetland: Wetland tag, if the object is a wetland 264 | sql: 265 | - minzoom: 10 266 | maxzoom: 14 267 | extent: 1024 268 | buffer: 4 269 | file: spirit/vegetation.sql.jinja2 270 | - minzoom: 15 271 | maxzoom: 15 272 | extent: 4096 273 | buffer: 16 274 | file: spirit/vegetation.sql.jinja2 275 | vegetation-names: 276 | description: vegetation names 277 | fields: 278 | vegetation: Type of vegetation. One of wood, heath, scrub, wetland, or grass 279 | wetland: Wetland tag, if the object is a wetland 280 | sql: 281 | - minzoom: 10 282 | maxzoom: 14 283 | extent: 256 284 | buffer: 4 285 | file: spirit/vegetation-names.sql.jinja2 286 | - minzoom: 15 287 | maxzoom: 15 288 | extent: 1024 289 | buffer: 16 290 | file: spirit/vegetation-names.sql.jinja2 291 | food: 292 | description: Food-related POIs 293 | fields: 294 | name: Name of food POI 295 | sql: 296 | - minzoom: 15 297 | maxzoom: 15 298 | extent: 1024 299 | buffer: 16 300 | file: spirit/food.sql.jinja2 301 | -------------------------------------------------------------------------------- /spirit/admin-names.sql.jinja2: -------------------------------------------------------------------------------- 1 | SELECT 2 | ST_AsMVTGeom(geom, {{unbuffered_bbox}}, {{extent}}, {{buffer}}) AS way, 3 | name, 4 | way_area 5 | FROM admin 6 | WHERE geom && {{bbox}} 7 | AND name IS NOT NULL 8 | AND way_area > 0.25 * {{tile_area}} 9 | {% if zoom <= 5 %} 10 | AND admin_level IN (2) 11 | {% elif zoom <= 8 %} 12 | AND admin_level IN (2, 3, 4) 13 | {% else %} 14 | AND admin_level IN (2, 3, 4, 5, 6) 15 | {% endif %} 16 | ORDER BY admin_level ASC, way_area DESC 17 | -------------------------------------------------------------------------------- /spirit/admin.sql.jinja2: -------------------------------------------------------------------------------- 1 | WITH boundaries AS ( 2 | SELECT 3 | geom, 4 | min_admin_level, 5 | multiple_relations 6 | FROM admin_lines 7 | WHERE geom && {{bbox}} 8 | {% if zoom >= 10 -%} 9 | AND min_admin_level IN (2, 3, 4, 5, 6, 7, 8) 10 | {% elif zoom >= 7 -%} 11 | AND min_admin_level IN (2, 3, 4, 5, 6) 12 | {% elif zoom >= 4 -%} 13 | AND min_admin_level IN (2, 3, 4) -- later filtered down more based on parent area 14 | {% else -%} 15 | AND min_admin_level = 2 16 | {% endif -%} 17 | ), adm2_boundaries AS ( 18 | SELECT 19 | geom, 20 | min_admin_level, 21 | NOT multiple_relations AS in_other_country -- if it wasn't in multiple relations, it had to of met the subquery for being in 2+ countries 22 | FROM boundaries b 23 | WHERE min_admin_level = 2 24 | AND (multiple_relations -- avoid line in polygon checks where they're not necessary. 25 | OR (SELECT COUNT(*) FROM admin p 26 | WHERE p.admin_level = 2 27 | AND ST_Covers(p.area, b.geom)) >= 2) 28 | ), other_boundaries AS ( 29 | SELECT 30 | geom, 31 | b.min_admin_level 32 | FROM boundaries b 33 | {% if zoom >= 6 -%} 34 | WHERE multiple_relations AND b.min_admin_level != 2 35 | {% else -%} 36 | WHERE multiple_relations AND b.min_admin_level NOT IN (2, 3, 4) -- 2 and 3,4 have special treatment 37 | OR (multiple_relations AND b.min_admin_level IN (3, 4) AND 38 | (SELECT true FROM admin p 39 | WHERE p.admin_level = 2 40 | AND p.way_area > 8e+12 41 | AND ST_Covers(p.area, b.geom) 42 | LIMIT 1)) 43 | {% endif -%} 44 | ), unioned_boundaries AS ( 45 | SELECT 46 | ST_LineMerge(ST_Collect(geom)) AS geom, 47 | min_admin_level, 48 | in_other_country 49 | FROM adm2_boundaries a 50 | GROUP BY min_admin_level, in_other_country 51 | UNION ALL 52 | SELECT 53 | ST_LineMerge(ST_Collect(geom)) AS geom, 54 | min_admin_level, 55 | NULL 56 | FROM other_boundaries 57 | GROUP BY min_admin_level 58 | ) 59 | SELECT 60 | {% if zoom >= 15 -%} 61 | ST_AsMVTGeom(geom, {{unbuffered_bbox}}, {{extent}}, {{buffer}}) AS way, 62 | {% else -%} 63 | ST_AsMVTGeom(ST_Simplify(geom, {{tile_length}}/750), {{unbuffered_bbox}}, {{extent}}, {{buffer}}) AS way, 64 | {% endif -%} 65 | min_admin_level AS admin_level, 66 | in_other_country 67 | FROM unioned_boundaries 68 | -------------------------------------------------------------------------------- /spirit/aeroways.sql.jinja2: -------------------------------------------------------------------------------- 1 | SELECT 2 | ST_AsMVTGeom((ST_Dump(ST_LineMerge(ST_Collect(geom)))).geom, {{unbuffered_bbox}}, {{extent}}, {{buffer}}) AS way, 3 | {% if zoom <= 12%} 4 | NULL as ref,-- No ref rendering on low zooms 5 | {% elif zoom <= 13 %} 6 | CASE WHEN aeroway = 'runway' THEN ref END AS ref, 7 | {% else %} 8 | ref, 9 | {% endif %} 10 | aeroway 11 | FROM aeroways 12 | WHERE geom && {{bbox}} 13 | GROUP BY aeroway, ref 14 | -------------------------------------------------------------------------------- /spirit/building-names.sql.jinja2: -------------------------------------------------------------------------------- 1 | SELECT 2 | ST_AsMVTGeom(point, {{unbuffered_bbox}}, {{extent}}, {{buffer}}) AS way, 3 | name, 4 | way_area 5 | FROM buildings 6 | WHERE point && {{bbox}} 7 | AND way_area > {{coordinate_area}}*16 8 | ORDER BY way_area DESC 9 | -------------------------------------------------------------------------------- /spirit/buildings.sql.jinja2: -------------------------------------------------------------------------------- 1 | SELECT 2 | ST_Collect(merged.geom) AS way 3 | {# 4 | The order of merging is important. If you ST_AsMVTGeom(ST_Collect) you can effectively get a 5 | ST_Union in some cases. To avoid this, we first transform to the VT coords, then collect. But 6 | because a geom might be a multipolygon or st_asmvtgeom could turn a polygon into a MP (e.g. 7 | way 391482237), we have to ST_Dump the geoms to turn multis into polys. Unfortunately, we can't 8 | just ST_Collect(ST_Dump(...)) because you can't mix an aggregate and set-returning function like 9 | this. So we have to do a LATERAL join. 10 | #} 11 | FROM buildings, 12 | LATERAL ST_Dump(ST_AsMVTGeom(geom, {{unbuffered_bbox}}, {{extent}}, {{buffer}})) AS merged 13 | WHERE buildings.geom && {{bbox}} 14 | AND way_area > {{coordinate_area}} 15 | -------------------------------------------------------------------------------- /spirit/education-names.sql.jinja2: -------------------------------------------------------------------------------- 1 | SELECT 2 | ST_AsMVTGeom(point, {{unbuffered_bbox}}, {{extent}}, {{buffer}}) AS way, 3 | name, 4 | education, 5 | way_area 6 | FROM education 7 | WHERE point && {{bbox}} AND name IS NOT NULL 8 | -------------------------------------------------------------------------------- /spirit/education.sql.jinja2: -------------------------------------------------------------------------------- 1 | SELECT 2 | ST_AsMVTGeom(ST_Collect(geom), {{unbuffered_bbox}}, {{extent}}, {{buffer}}) AS way, 3 | education 4 | FROM education 5 | WHERE geom && {{bbox}} 6 | GROUP BY education 7 | -------------------------------------------------------------------------------- /spirit/food.sql.jinja2: -------------------------------------------------------------------------------- 1 | SELECT 2 | ST_AsMVTGeom(point, {{unbuffered_bbox}}, {{extent}}, {{buffer}}) AS way, 3 | name, 4 | food 5 | FROM food 6 | WHERE point && {{bbox}} 7 | -------------------------------------------------------------------------------- /spirit/landuse-names.sql.jinja2: -------------------------------------------------------------------------------- 1 | SELECT 2 | ST_AsMVTGeom(point, {{unbuffered_bbox}}, {{extent}}, {{buffer}}) AS way, 3 | name, 4 | landuse, 5 | way_area 6 | FROM landuse 7 | WHERE point && {{bbox}} 8 | AND name IS NOT NULL 9 | -------------------------------------------------------------------------------- /spirit/landuse.sql.jinja2: -------------------------------------------------------------------------------- 1 | SELECT 2 | ST_AsMVTGeom(ST_Collect(geom), {{unbuffered_bbox}}, {{extent}}, {{buffer}}) AS way, 3 | landuse 4 | FROM landuse 5 | WHERE geom && {{bbox}} 6 | GROUP BY landuse 7 | -------------------------------------------------------------------------------- /spirit/leisure-names.sql.jinja2: -------------------------------------------------------------------------------- 1 | SELECT 2 | ST_AsMVTGeom(point, {{unbuffered_bbox}}, {{extent}}, {{buffer}}) AS way, 3 | name, 4 | way_area 5 | FROM leisure 6 | WHERE point && {{bbox}} 7 | AND name IS NOT NULL 8 | AND leisure = 'park' 9 | -------------------------------------------------------------------------------- /spirit/leisure.sql.jinja2: -------------------------------------------------------------------------------- 1 | SELECT 2 | ST_AsMVTGeom(ST_Collect(geom), {{unbuffered_bbox}}, {{extent}}, {{buffer}}) AS way, 3 | leisure 4 | FROM leisure 5 | WHERE geom && {{bbox}} 6 | AND ( 7 | {% if zoom <= 14 %} 8 | leisure IN ('park', 'stadium') 9 | {% else %} 10 | leisure IN ('park', 'stadium', 'playground') 11 | {% endif %} 12 | ) 13 | GROUP BY leisure 14 | -------------------------------------------------------------------------------- /spirit/railways-high.sql.jinja2: -------------------------------------------------------------------------------- 1 | WITH merged_rail AS ( 2 | SELECT 3 | ST_LineMerge(ST_Collect(geom)) AS way, 4 | railway, 5 | COALESCE(minor, FALSE) AS minor, 6 | COALESCE(tunnel, FALSE) AS tunnel, 7 | COALESCE(bridge, FALSE) AS bridge, 8 | COALESCE(layer, 0) AS layer, 9 | z_order 10 | FROM railways 11 | WHERE geom && {{bbox}} 12 | GROUP BY railway, minor, tunnel, bridge, layer, z_order 13 | ) 14 | SELECT 15 | ST_AsMVTGeom((ST_Dump(way)).geom, {{unbuffered_bbox}}, {{extent}}, {{buffer}}) AS way, 16 | railway, minor, tunnel, bridge, layer, z_order 17 | FROM merged_rail 18 | ORDER BY layer, z_order 19 | -------------------------------------------------------------------------------- /spirit/roads-high.sql.jinja2: -------------------------------------------------------------------------------- 1 | WITH all_roads AS ( 2 | SELECT 3 | CASE WHEN oneway = '-1' THEN ST_Reverse(geom) ELSE geom END AS geom, 4 | name, 5 | ref, 6 | CASE -- normalize highway values 7 | WHEN highway IN ('motorway', 'motorway_link') THEN 'motorway' 8 | WHEN highway IN ('trunk', 'trunk_link') THEN 'trunk' 9 | WHEN highway IN ('primary', 'primary_link') THEN 'primary' 10 | WHEN highway IN ('secondary', 'secondary_link') THEN 'secondary' 11 | WHEN highway IN ('tertiary', 'tertiary_link') THEN 'tertiary' 12 | WHEN highway IN ('unclassified', 'residential', 'living_street', 'service', 'bridleway', 'footway', 'cycleway', 'path') THEN highway 13 | END as highway, 14 | CASE WHEN highway IN ('motorway_link', 'trunk_link', 'primary_link', 'secondary_link', 'tertiary_link') THEN true END AS link, 15 | minor, 16 | CASE 17 | WHEN oneway = '-1' THEN 'yes' 18 | WHEN oneway = 'no' THEN NULL 19 | ELSE oneway END AS oneway, 20 | tunnel, 21 | bridge, 22 | layer, 23 | (SELECT ref FROM road_routes 24 | WHERE roads.way_id = road_routes.member_id 25 | ORDER BY cardinality(string_to_array(network, ':')) ASC, char_length(network) ASC, network, road_routes.relation_id 26 | LIMIT 1) AS route_ref, 27 | z_order 28 | FROM roads 29 | WHERE geom && {{bbox}} 30 | {% if zoom <= 10 %} 31 | AND highway IN ('motorway', 'motorway_link', 'trunk', 'trunk_link', 'primary', 'primary_link', 32 | 'secondary', 'secondary_link', 'tertiary', 'tertiary_link') 33 | {% elif zoom <= 13 %} 34 | AND highway IN ('motorway', 'motorway_link', 'trunk', 'trunk_link', 'primary', 'primary_link', 35 | 'secondary', 'secondary_link', 'tertiary', 'tertiary_link', 'unclassified', 'residential', 36 | 'living_street') 37 | {% else %} 38 | AND highway IN ('motorway', 'motorway_link', 'trunk', 'trunk_link', 'primary', 'primary_link', 39 | 'secondary', 'secondary_link', 'tertiary', 'tertiary_link', 'unclassified', 'residential', 40 | 'living_street', 'service', 'bridleway', 'footway', 'cycleway', 'path') 41 | {% endif %} 42 | ), reffed_roads AS ( 43 | SELECT 44 | geom, 45 | {# Omitting name and ref from small roads allows for better merging and smaller tiles #} 46 | {% if zoom <= 11 %} 47 | CASE WHEN highway IN ('motorway', 'trunk', 'primary', 'secondary') THEN name END AS name, 48 | CASE WHEN highway IN ('motorway', 'trunk', 'primary', 'secondary') THEN COALESCE(route_ref, ref) END AS ref, 49 | {% elif zoom <= 12 %} 50 | CASE WHEN highway IN ('motorway', 'trunk', 'primary', 'secondary', 'tertiary') THEN name END AS name, 51 | CASE WHEN highway IN ('motorway', 'trunk', 'primary', 'secondary', 'tertiary') THEN COALESCE(route_ref, ref) END AS ref, 52 | {% else %} 53 | name, 54 | COALESCE(route_ref, ref) AS ref, 55 | {% endif %} 56 | highway, 57 | link, 58 | minor, 59 | oneway, 60 | tunnel, 61 | bridge, 62 | layer, 63 | z_order 64 | FROM all_roads 65 | ), oneway_roads AS ( 66 | SELECT 67 | geom, -- linemerge with geos 3.11+ when we require it 68 | name, 69 | highway, 70 | link, 71 | minor, 72 | ref, 73 | oneway, 74 | tunnel, 75 | bridge, 76 | layer, 77 | z_order 78 | FROM reffed_roads 79 | WHERE oneway = 'yes' 80 | ), other_roads AS ( 81 | SELECT 82 | ST_LineMerge(ST_Collect(geom)) AS geom, 83 | name, 84 | highway, 85 | link, 86 | minor, 87 | ref, 88 | oneway, 89 | tunnel, 90 | bridge, 91 | layer, 92 | z_order 93 | FROM reffed_roads 94 | WHERE oneway IS DISTINCT FROM 'yes' 95 | GROUP BY name, highway, link, minor, ref, oneway, tunnel, bridge, layer, z_order 96 | ), grouped_roads AS ( 97 | SELECT 98 | geom, name, highway, link, minor, ref, oneway, tunnel, bridge, layer, z_order 99 | FROM oneway_roads 100 | UNION ALL 101 | SELECT 102 | (ST_Dump(geom)).geom AS way, name, highway, link, minor, ref, oneway, tunnel, bridge, layer, z_order 103 | FROM other_roads 104 | ) 105 | SELECT 106 | ST_AsMVTGeom(geom, {{unbuffered_bbox}}, {{extent}}, {{buffer}}) AS way, 107 | name, highway, link, minor, ref, oneway, tunnel, bridge, layer, z_order 108 | FROM grouped_roads 109 | ORDER BY z_order 110 | -------------------------------------------------------------------------------- /spirit/settlements.sql.jinja2: -------------------------------------------------------------------------------- 1 | SELECT 2 | ST_AsMVTGeom(geom, {{unbuffered_bbox}}, {{extent}}, {{buffer}}) AS way, 3 | name, 4 | place 5 | FROM settlements 6 | WHERE geom && {{bbox}} 7 | AND name IS NOT NULL 8 | {% if zoom <= 9 %} 9 | AND place IN ('city') 10 | {% elif zoom <= 10 %} 11 | AND place IN ('city', 'town') 12 | {% elif zoom <= 11 %} 13 | AND place IN ('city', 'town', 'village') 14 | {% elif zoom <= 12 %} 15 | AND place IN ('city', 'town', 'village', 'hamlet') 16 | {% else %} 17 | AND place IN ('city', 'town', 'village', 'hamlet', 'isolated_dwelling') 18 | {% endif %} 19 | -------------------------------------------------------------------------------- /spirit/transit-points.sql.jinja2: -------------------------------------------------------------------------------- 1 | WITH transit_normalized AS ( 2 | SELECT 3 | geom, 4 | name, 5 | station, 6 | mode 7 | FROM transit 8 | WHERE geom && {{bbox}} 9 | AND ( 10 | {% if zoom >= 15 %} 11 | mode = 'bus' OR 12 | {% elif zoom >= 13 %} 13 | (mode = 'bus' AND station) OR 14 | {% endif %} 15 | {% if zoom >= 14 %} 16 | (mode = 'tram' AND NOT station) OR 17 | {% endif %} 18 | mode = 'subway' OR mode = 'airplane') 19 | ) 20 | SELECT 21 | ST_AsMVTGeom(geom, {{unbuffered_bbox}}, {{extent}}, {{buffer}}) AS way, 22 | name, 23 | station, 24 | mode 25 | FROM transit_normalized 26 | ORDER BY station DESC, 27 | CASE mode 28 | WHEN 'airplane' THEN 1 29 | WHEN 'subway' THEN 3 30 | WHEN 'tram' THEN 4 31 | WHEN 'bus' THEN 5 32 | ELSE 10 33 | END 34 | -------------------------------------------------------------------------------- /spirit/vegetation-names.sql.jinja2: -------------------------------------------------------------------------------- 1 | SELECT 2 | ST_AsMVTGeom(geom, {{unbuffered_bbox}}, {{extent}}, {{buffer}}) AS way, 3 | name, 4 | vegetation, 5 | wetland, 6 | way_area 7 | FROM vegetation 8 | WHERE point && {{bbox}} 9 | -------------------------------------------------------------------------------- /spirit/vegetation.sql.jinja2: -------------------------------------------------------------------------------- 1 | SELECT 2 | ST_AsMVTGeom(ST_Collect(geom), {{unbuffered_bbox}}, {{extent}}, {{buffer}}) AS way, 3 | vegetation 4 | FROM vegetation 5 | WHERE geom && {{bbox}} 6 | GROUP BY vegetation 7 | -------------------------------------------------------------------------------- /spirit/water-lines.sql.jinja2: -------------------------------------------------------------------------------- 1 | SELECT 2 | ST_AsMVTGeom(ST_RemoveRepeatedPoints(ST_LineMerge(ST_Collect(geom)), 4*{{coordinate_length}}), {{unbuffered_bbox}}, {{extent}}, {{buffer}}) AS way, 3 | name, 4 | waterway 5 | FROM waterways 6 | WHERE geom && {{bbox}} 7 | {% if zoom <= 10 %} 8 | AND waterway = 'river' 9 | {% elif zoom <= 12 %} 10 | AND waterway IN ('river', 'canal') 11 | {% elif zoom <= 13 %} 12 | AND waterway IN ('river', 'canal', 'stream') 13 | {% else %} 14 | AND waterway IN ('river', 'canal', 'stream', 'drain', 'ditch') 15 | {% endif %} 16 | GROUP BY waterway, name 17 | -------------------------------------------------------------------------------- /spirit/water-names.sql.jinja2: -------------------------------------------------------------------------------- 1 | SELECT 2 | ST_AsMVTGeom(point, {{unbuffered_bbox}}, {{extent}}, {{buffer}}) AS way, 3 | name, 4 | way_area 5 | FROM water 6 | WHERE point && {{bbox}} 7 | AND way_area > 16 * {{coordinate_area}} 8 | ORDER BY way_area DESC 9 | -------------------------------------------------------------------------------- /spirit/water.sql.jinja2: -------------------------------------------------------------------------------- 1 | SELECT 2 | ST_AsMVTGeom(way, {{unbuffered_bbox}}, {{extent}}, {{buffer}}) AS way 3 | {% if zoom <= 8 %} 4 | FROM simplified_water_polygons 5 | {% else %} 6 | FROM water_polygons 7 | {% endif %} 8 | WHERE way && {{bbox}} 9 | UNION ALL 10 | SELECT 11 | ST_AsMVTGeom(ST_Collect(geom), {{unbuffered_bbox}}, {{extent}}, {{buffer}}) AS way 12 | FROM water 13 | WHERE geom && {{bbox}} 14 | AND way_area > 16 * {{coordinate_area}} 15 | -------------------------------------------------------------------------------- /sprites/airport.svg: -------------------------------------------------------------------------------- 1 | 6 | 8 | 11 | 12 | 13 | 17 | 18 | -------------------------------------------------------------------------------- /sprites/bus_station.svg: -------------------------------------------------------------------------------- 1 | 6 | 8 | 11 | 12 | 13 | 17 | 18 | -------------------------------------------------------------------------------- /sprites/bus_stop.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /sprites/food.svg: -------------------------------------------------------------------------------- 1 | 6 | 8 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /sprites/subway.svg: -------------------------------------------------------------------------------- 1 | 6 | 8 | 11 | 12 | 13 | 17 | 18 | -------------------------------------------------------------------------------- /sprites/train.svg: -------------------------------------------------------------------------------- 1 | 6 | 8 | 11 | 12 | 13 | 17 | 18 | -------------------------------------------------------------------------------- /sprites/wetland.md: -------------------------------------------------------------------------------- 1 | 2 | Wetland patterns are built from two separately generated pattern files by means of raster processing. The basic principle is shown by the following ImageMagick commands: 3 | 4 | ``` 5 | convert -density 720 pattern.svg -morphology Erode Disk:5.3 \( +clone -fill black -draw 'color 0,0 floodfill' -negate \) +swap -morphology Erode Disk:10.3 -compose Darken -composite -scale 12.5% -depth 8 pattern_casing.png 6 | 7 | convert -depth 8 -density 90 wetland.svg wetland_tile.png 8 | 9 | montage wetland_tile.png wetland_tile.png wetland_tile.png wetland_tile.png -geometry 256x256+0+0 wetland_512.png 10 | 11 | convert wetland_512.png \( pattern_casing.png -negate \) -compose Lighten -composite -threshold 50% \( +clone -negate -morphology hitandmiss peaks:1.9 \) -compose Lighten -composite +level 20%,100% wetland_pattern_bkg.png 12 | 13 | convert -depth 8 -size 512x512 xc:"$SYMBOL" \( pattern.png -negate \) -set colorspace RGB -alpha Off -compose CopyOpacity -composite pattern_col.png 14 | 15 | convert -depth 8 -size 512x512 xc:"$WETLAND" \( wetland_pattern_bkg.png -negate \) -set colorspace RGB -alpha Off -compose CopyOpacity -composite +compose pattern_col.png -compose Over -composite wetland_pattern.png 16 | ``` 17 | 18 | In some cases, which has not been elucidated (https://github.com/gravitystorm/openstreetmap-carto/pull/3051), the SVG conversions produce files with erroneous sizes. In this case, the following command sequence may work, by using Inkscape to rasterize the SVGs: 19 | 20 | ``` 21 | inkscape -z --export-png=swamp.png --export-dpi=96 --export-background=white swamp.svg 22 | 23 | inkscape -z --export-png=swamp_hr.png --export-dpi=768 --export-background=white swamp.svg 24 | 25 | convert swamp_hr.png -morphology Erode Disk:5.3 \( +clone -fill black -draw 'color 0,0 floodfill' -negate \) +swap -morphology Erode Disk:10.3 -compose Darken -composite -scale 12.5% -depth 8 pattern_casing.png 26 | 27 | inkscape -z --export-png=wetland_tile.png --export-dpi=96 --export-background=white wetland.svg 28 | 29 | montage wetland_tile.png wetland_tile.png wetland_tile.png wetland_tile.png -geometry 256x256+0+0 wetland_512.png 30 | 31 | convert wetland_512.png \( pattern_casing.png -negate \) -compose Lighten -composite -threshold 50% \( +clone -negate -morphology hitandmiss peaks:1.9 \) -compose Lighten -composite +level 20%,100% wetland_pattern_bkg.png 32 | 33 | convert -depth 8 -size 512x512 xc:"#93b685" \( swamp.png -negate \) -set colorspace RGB -alpha Off -compose CopyOpacity -composite pattern_col.png 34 | 35 | convert -depth 8 -size 512x512 xc:"#4aa5fa" \( wetland_pattern_bkg.png -negate \) -set colorspace RGB -alpha Off -compose CopyOpacity -composite +compose pattern_col.png -compose Over -composite +gamma - -strip wetland_pattern.png 36 | ``` 37 | -------------------------------------------------------------------------------- /style.yaml: -------------------------------------------------------------------------------- 1 | name: Spirit 2 | version: 8 3 | sources: 4 | spirit: 5 | type: vector 6 | url: http://127.0.0.1:8000/tilejson.json 7 | sprite: http://127.0.0.1:8081/sprites 8 | glyphs: https://pnorman.dev.openstreetmap.org/spirit/fonts/{fontstack}/{range}.pbf 9 | layers: 10 | - !!inc/file style/background.yaml 11 | - !!inc/file style/landuse.yaml 12 | - !!inc/file style/education.yaml 13 | - !!inc/file style/education-outline.yaml 14 | - !!inc/file style/leisure.yaml 15 | - !!inc/file style/vegetation.yaml 16 | - !!inc/file style/vegetation-pattern.yaml 17 | - !!inc/file style/water.yaml 18 | - !!inc/file style/water-lines.yaml 19 | - !!inc/file style/building-fill.yaml 20 | - !!inc/file style/building-outline.yaml 21 | - !!inc/file style/road-base-casing.yaml 22 | - !!inc/file style/road-tunnel-casing.yaml 23 | - !!inc/file style/road-base-fill.yaml 24 | - !!inc/file style/road-casing.yaml 25 | - !!inc/file style/road-fill.yaml 26 | - !!inc/file style/rail-casing.yaml 27 | - !!inc/file style/rail-fill.yaml 28 | - !!inc/file style/road-bridge-casing.yaml 29 | - !!inc/file style/road-bridge-fill.yaml 30 | - !!inc/file style/rail-bridge-outer-casing.yaml 31 | - !!inc/file style/rail-bridge-casing.yaml 32 | - !!inc/file style/rail-bridge-fill.yaml 33 | - !!inc/file style/aeroways.yaml 34 | - !!inc/file style/transit-points.yaml 35 | - !!inc/file style/admin-background.yaml 36 | - !!inc/file style/admin-background-dashed.yaml 37 | - !!inc/file style/admin-2.yaml 38 | - !!inc/file style/admin-4.yaml 39 | - !!inc/file style/admin-6.yaml 40 | - !!inc/file style/admin-8.yaml 41 | - !!inc/file style/settlement-names.yaml 42 | - !!inc/file style/admin-names.yaml 43 | - !!inc/file style/aeroway-text.yaml 44 | - !!inc/file style/road-text.yaml 45 | - !!inc/file style/water-line-text.yaml 46 | - !!inc/file style/water-names.yaml 47 | - !!inc/file style/vegetation-names.yaml 48 | - !!inc/file style/leisure-names.yaml 49 | - !!inc/file style/education-names.yaml 50 | - !!inc/file style/landuse-names.yaml 51 | - !!inc/file style/food.yaml 52 | - !!inc/file style/building-names.yaml 53 | -------------------------------------------------------------------------------- /style/admin-2.yaml: -------------------------------------------------------------------------------- 1 | id: admin-2 2 | source: spirit 3 | source-layer: admin 4 | type: line 5 | filter: [==, [get, admin_level], 2] 6 | layout: 7 | line-join: bevel 8 | line-cap: square 9 | paint: 10 | line-color: black 11 | line-width: 12 | - interpolate 13 | - [exponential, 1.35] 14 | - [zoom] 15 | - 4 16 | - 1.1 17 | - 12 18 | - 1.8 19 | - 20 20 | - 4 21 | line-dasharray: [12, 3, 4, 3, 4, 3] 22 | -------------------------------------------------------------------------------- /style/admin-4.yaml: -------------------------------------------------------------------------------- 1 | id: admin-4 2 | source: spirit 3 | source-layer: admin 4 | type: line 5 | filter: [in, [get, admin_level], [literal, [3, 4]]] 6 | layout: 7 | line-join: bevel 8 | line-cap: square 9 | paint: 10 | line-color: black 11 | line-width: 12 | - interpolate 13 | - [exponential, 1.35] 14 | - [zoom] 15 | - 4 16 | - .9 17 | - 12 18 | - 1.2 19 | - 20 20 | - 3 21 | line-dasharray: [10, 3] 22 | -------------------------------------------------------------------------------- /style/admin-6.yaml: -------------------------------------------------------------------------------- 1 | id: admin-6 2 | source: spirit 3 | source-layer: admin 4 | type: line 5 | filter: [in, [get, admin_level], [literal, [5, 6]]] 6 | layout: 7 | line-join: bevel 8 | line-cap: square 9 | paint: 10 | line-color: black 11 | line-width: 12 | - interpolate 13 | - [exponential, 1.35] 14 | - [zoom] 15 | - 4 16 | - .6 17 | - 12 18 | - 1 19 | - 20 20 | - 3 21 | line-dasharray: [7, 2, 3, 2] 22 | -------------------------------------------------------------------------------- /style/admin-8.yaml: -------------------------------------------------------------------------------- 1 | id: admin-8 2 | source: spirit 3 | source-layer: admin 4 | type: line 5 | filter: [in, [get, admin_level], [literal, [7, 8]]] 6 | layout: 7 | line-join: bevel 8 | line-cap: square 9 | paint: 10 | line-color: black 11 | line-width: 12 | - interpolate 13 | - [exponential, 1.3] 14 | - [zoom] 15 | - 10 16 | - 1 17 | - 20 18 | - 2.5 19 | line-dasharray: [3, 3] 20 | -------------------------------------------------------------------------------- /style/admin-background-dashed.yaml: -------------------------------------------------------------------------------- 1 | id: admin-background-dashed 2 | source: spirit 3 | source-layer: admin 4 | type: line 5 | layout: 6 | line-join: bevel 7 | line-cap: square 8 | filter: [all, [get, in_other_country], [==, [get, admin_level], 2]] 9 | paint: 10 | line-dasharray: [1.0, 1.0] 11 | line-color: rgba(194,94,184, 0.6) # lch(55,60,330) 12 | line-width: 13 | - interpolate 14 | - [exponential, 1.35] 15 | - [zoom] 16 | - 4 17 | - - match 18 | - [get, admin_level] 19 | - 2 20 | - 5 21 | - 4 22 | - 3 23 | - 6 24 | - 0 25 | - 3 26 | - 10 27 | - - match 28 | - [get, admin_level] 29 | - 2 30 | - 10 31 | - 4 32 | - 7 33 | - 6 34 | - 6 35 | - 3 36 | - 20 37 | - - match 38 | - [get, admin_level] 39 | - 2 40 | - 30 41 | - 4 42 | - 15 43 | - 6 44 | - 10 45 | - 3 46 | 47 | -------------------------------------------------------------------------------- /style/admin-background.yaml: -------------------------------------------------------------------------------- 1 | id: admin-background 2 | source: spirit 3 | source-layer: admin 4 | type: line 5 | layout: 6 | line-join: bevel 7 | line-cap: square 8 | filter: [any, ['!=', [get, admin_level], 2], ['!', [get, in_other_country]]] 9 | paint: 10 | line-color: rgba(194,94,184, 0.6) # lch(55,60,330) 11 | line-width: 12 | - interpolate 13 | - [exponential, 1.35] 14 | - [zoom] 15 | - 4 16 | - - match 17 | - [get, admin_level] 18 | - 2 19 | - 5 20 | - 4 21 | - 3 22 | - 6 23 | - 0 24 | - 3 25 | - 10 26 | - - match 27 | - [get, admin_level] 28 | - 2 29 | - 10 30 | - 4 31 | - 7 32 | - 6 33 | - 6 34 | - 3 35 | - 20 36 | - - match 37 | - [get, admin_level] 38 | - 2 39 | - 30 40 | - 4 41 | - 15 42 | - 6 43 | - 10 44 | - 3 45 | 46 | -------------------------------------------------------------------------------- /style/admin-names.yaml: -------------------------------------------------------------------------------- 1 | id: admin-names 2 | source: spirit 3 | source-layer: admin-names 4 | type: symbol 5 | paint: 6 | text-color: '#5a0756' # lch(35,70,330) 7 | text-halo-color: '#efddec' # lch(90,10,330) 8 | text-halo-width: 1.5 9 | text-halo-blur: 1 10 | layout: 11 | text-font: !!inc/file style/inc/regular-font.yaml 12 | text-field: '{name}' 13 | symbol-placement: point 14 | text-size: 14 15 | filter: 16 | - '>=' 17 | - [get, way_area] 18 | - ['*', 750, 6126430366.1, ['^', 0.25, [zoom]]] 19 | -------------------------------------------------------------------------------- /style/aeroway-text.yaml: -------------------------------------------------------------------------------- 1 | id: aeroway-text 2 | source: spirit 3 | source-layer: aeroways 4 | type: symbol 5 | paint: 6 | text-color: '#4d5262' #lch(35,10,280) 7 | text-halo-color: '#eef0fa' #lch(95,5,280) 8 | text-halo-width: 1.5 9 | text-halo-blur: 1 10 | minzoom: 12 11 | layout: 12 | text-font: !!inc/file style/inc/regular-font.yaml 13 | text-field: '{ref}' 14 | symbol-placement: line 15 | text-size: 16 | - interpolate 17 | - - exponential 18 | - 1.35 19 | - - zoom 20 | - 12 21 | - - match 22 | - - get 23 | - aeroway 24 | - runway 25 | - 10 26 | - 10 27 | - 20 28 | - - match 29 | - - get 30 | - aeroway 31 | - runway 32 | - 18 33 | - 14 34 | symbol-spacing: 400 35 | text-letter-spacing: 0.2 36 | -------------------------------------------------------------------------------- /style/aeroways.yaml: -------------------------------------------------------------------------------- 1 | id: aeroways 2 | source: spirit 3 | source-layer: aeroways 4 | type: line 5 | paint: 6 | line-color: '#b3b8cb' # lch(75,10,280) 7 | line-width: 8 | - interpolate 9 | - - exponential 10 | - 1.2 11 | - - zoom 12 | - 12 13 | - - match 14 | - [get, aeroway] 15 | - runway 16 | - 4 17 | - 2 18 | - 20 19 | - - match 20 | - [get, aeroway] 21 | - runway 22 | - 72 23 | - 18 24 | layout: 25 | line-cap: butt 26 | -------------------------------------------------------------------------------- /style/background.yaml: -------------------------------------------------------------------------------- 1 | id: background 2 | type: background 3 | layout: 4 | visibility: visible 5 | paint: 6 | background-color: '#eff2ec' # lch(95,3,130) 7 | -------------------------------------------------------------------------------- /style/building-fill.yaml: -------------------------------------------------------------------------------- 1 | id: building-fill 2 | source: spirit 3 | source-layer: buildings 4 | layout: 5 | visibility: visible 6 | minzoom: 13 7 | type: fill 8 | paint: 9 | fill-color: '#d9d5c2' # lch(85,10,100) 10 | -------------------------------------------------------------------------------- /style/building-names.yaml: -------------------------------------------------------------------------------- 1 | id: building-names 2 | source: spirit 3 | source-layer: building-names 4 | minzoom: 13 5 | type: symbol 6 | filter: 7 | - '>=' 8 | - [get, way_area] 9 | - ['*', 750, 6126430366.1, ['^', 0.25, [zoom]]] 10 | layout: 11 | text-field: '{name}' 12 | text-font: !!inc/file style/inc/regular-font.yaml 13 | text-size: 12 14 | text-max-width: 15 | - interpolate 16 | - [linear] 17 | - [length, [get, name]] 18 | - 20 19 | - 7 20 | - 40 21 | - 10 22 | - 60 23 | - 15 24 | paint: 25 | text-color: '#3e3c2c' # lch(25,10,100) 26 | text-halo-width: 1 27 | text-halo-color: '#d9d5c2' # lch(85,10,100) 28 | -------------------------------------------------------------------------------- /style/building-outline.yaml: -------------------------------------------------------------------------------- 1 | id: building-outline 2 | source: spirit 3 | source-layer: buildings 4 | type: line 5 | paint: 6 | line-color: '#c1b994' # lch(75,20,100) 7 | line-width: 1 8 | -------------------------------------------------------------------------------- /style/education-names.yaml: -------------------------------------------------------------------------------- 1 | id: education-names 2 | source: spirit 3 | source-layer: education-names 4 | minzoom: 13 5 | type: symbol 6 | filter: 7 | - '>=' 8 | - [get, way_area] 9 | - ['*', 1500, 6126430366.1, ['^', 0.25, [zoom]]] 10 | layout: 11 | text-field: '{name}' 12 | text-font: !!inc/file style/inc/regular-font.yaml 13 | text-size: 12 14 | text-max-width: 15 | - interpolate 16 | - [linear] 17 | - [length, [get, name]] 18 | - 20 19 | - 7 20 | - 40 21 | - 10 22 | - 60 23 | - 15 24 | paint: 25 | text-color: '#353109' # lch(20,25,100) 26 | text-halo-width: 1 27 | text-halo-color: '#ece3b3' # lch(90,25,100) 28 | -------------------------------------------------------------------------------- /style/education-outline.yaml: -------------------------------------------------------------------------------- 1 | id: education-outline 2 | source: spirit 3 | source-layer: education 4 | type: line 5 | paint: 6 | line-color: '#cdc7ab' # lch(80,15,100) 7 | line-width: 1 8 | -------------------------------------------------------------------------------- /style/education.yaml: -------------------------------------------------------------------------------- 1 | id: education 2 | source: spirit 3 | source-layer: education 4 | type: fill 5 | layout: 6 | fill-sort-key: 7 | - match 8 | - [get, education] 9 | - kindergarten 10 | - 4 11 | - school 12 | - 3 13 | - college 14 | - 2 15 | - university 16 | - 1 17 | - 0 18 | paint: 19 | fill-color: '#ece3b3' # lch(90,25,100) 20 | -------------------------------------------------------------------------------- /style/food.yaml: -------------------------------------------------------------------------------- 1 | id: food 2 | source: spirit 3 | source-layer: food 4 | type: symbol 5 | layout: 6 | icon-image: food 7 | icon-allow-overlap: false 8 | icon-size: 1 9 | text-optional: true 10 | text-field: '{name}' 11 | text-font: !!inc/file style/inc/regular-font.yaml 12 | text-size: 11 13 | text-offset: [0, 0.45] 14 | text-anchor: top 15 | symbol-sort-key: 16 | - match 17 | - [get, food] 18 | - restaurant 19 | - 10 20 | - 100 21 | paint: 22 | text-color: '#663d06' # lch(30,40,70) 23 | text-halo-width: 1 24 | text-halo-color: '#f1e0d1' 25 | -------------------------------------------------------------------------------- /style/inc/regular-font.yaml: -------------------------------------------------------------------------------- 1 | - Noto Sans Regular 2 | -------------------------------------------------------------------------------- /style/inc/road-casing-color.yaml: -------------------------------------------------------------------------------- 1 | - match 2 | - [get, highway] 3 | - motorway 4 | - '#b40017' # lch(35,80,30) 5 | - trunk 6 | - '#bd5c00' # lch(50,70,60) 7 | - primary 8 | - '#bd5c00' # lch(50,70,60) 9 | - secondary 10 | - '#c9952c' # lch(65,60,80) 11 | - tertiary 12 | - '#c9952c' # lch(65,60,80) 13 | - unclassified 14 | - '#b7bab4' # lch(75,3,130) 15 | - residential 16 | - '#b7bab4' # lch(75,3,130) 17 | - living_street 18 | - '#b7bab4' # lch(75,3,130) 19 | - service 20 | - '#b7bab4' # lch(75,3,130) 21 | - footway 22 | - '#696b67' # lch(75,3,130) 23 | - cycleway 24 | - '#696b67' # lch(75,3,130) 25 | - red 26 | -------------------------------------------------------------------------------- /style/inc/road-fill-color.yaml: -------------------------------------------------------------------------------- 1 | - match 2 | - [get, highway] 3 | - motorway 4 | - '#e52838' # lch(50,80,30) 5 | - trunk 6 | - '#ec822c' # lch(65,70,60) 7 | - primary 8 | - '#ec822c' # lch(65,70,60) 9 | - secondary 10 | - '#f7bd55' # lch(80,60,80) 11 | - tertiary 12 | - '#f7bd55' # lch(80,60,80) 13 | - unclassified 14 | - '#e0e3de' # lch(90,3,130) 15 | - residential 16 | - '#e0e3de' # lch(90,3,130) 17 | - living_street 18 | - '#e0e3de' # lch(90,3,130) 19 | - service 20 | - '#e0e3de' # lch(90,3,130) 21 | - red 22 | -------------------------------------------------------------------------------- /style/landuse-names.yaml: -------------------------------------------------------------------------------- 1 | id: landuse-names 2 | source: spirit 3 | source-layer: landuse-names 4 | minzoom: 13 5 | type: symbol 6 | filter: 7 | - '>=' 8 | - [get, way_area] 9 | - ['*', 1500, 6126430366.1, ['^', 0.25, [zoom]]] 10 | layout: 11 | text-field: '{name}' 12 | text-font: !!inc/file style/inc/regular-font.yaml 13 | text-size: 12 14 | text-max-width: 15 | - interpolate 16 | - [linear] 17 | - [length, [get, name]] 18 | - 20 19 | - 7 20 | - 40 21 | - 10 22 | - 60 23 | - 15 24 | paint: 25 | text-color: black 26 | text-halo-width: 1 27 | text-halo-color: 28 | - match 29 | - [get, landuse] 30 | - residential 31 | - '#f9f0dd' # lch(95,10,90) 32 | - commercial 33 | - '#fde2e5' # lch(92,10,10) 34 | - retail 35 | - '#fde2e5' # lch(92,10,10) 36 | - industrial 37 | - '#e0e3de' # lch(90,3,130) 38 | - red 39 | -------------------------------------------------------------------------------- /style/landuse.yaml: -------------------------------------------------------------------------------- 1 | id: landuse 2 | source: spirit 3 | source-layer: landuse 4 | type: fill 5 | layout: 6 | fill-sort-key: 7 | - match 8 | - [get, landuse] 9 | - commercial 10 | - 4 11 | - retail 12 | - 3 13 | - residential 14 | - 2 15 | - industrial 16 | - 1 17 | - 0 18 | paint: 19 | fill-color: 20 | - match 21 | - [get, landuse] 22 | - residential 23 | - '#f9f0dd' # lch(95,10,90) 24 | - commercial 25 | - '#fde2e5' # lch(92,10,10) 26 | - retail 27 | - '#fde2e5' # lch(92,10,10) 28 | - industrial 29 | - '#e0e3de' # lch(90,3,130) 30 | - red 31 | -------------------------------------------------------------------------------- /style/leisure-names.yaml: -------------------------------------------------------------------------------- 1 | id: leisure-names 2 | source: spirit 3 | source-layer: leisure-names 4 | minzoom: 13 5 | type: symbol 6 | filter: 7 | - '>=' 8 | - [get, way_area] 9 | - ['*', 1500, 6126430366.1, ['^', 0.25, [zoom]]] 10 | layout: 11 | text-field: '{name}' 12 | text-font: !!inc/file style/inc/regular-font.yaml 13 | text-size: 12 14 | text-max-width: 15 | - interpolate 16 | - [linear] 17 | - [length, [get, name]] 18 | - 20 19 | - 7 20 | - 40 21 | - 10 22 | - 60 23 | - 15 24 | paint: 25 | text-color: '#20342e' # lch(20,10,170) 26 | text-halo-width: 1 27 | text-halo-color: '#cfe8df' # lch(90,10,170) 28 | -------------------------------------------------------------------------------- /style/leisure.yaml: -------------------------------------------------------------------------------- 1 | id: leisure 2 | source: spirit 3 | source-layer: leisure 4 | type: fill 5 | filter: 6 | - in 7 | - [get, leisure] 8 | - [literal, [park, stadium]] 9 | layout: 10 | fill-sort-key: 11 | - match 12 | - [get, leisure] 13 | - park 14 | - 20 15 | - stadium 16 | - 10 17 | - playground 18 | - 3 19 | - 0 20 | paint: 21 | fill-color: '#cfe8df' # lch(90,10,170) 22 | fill-outline-color: '#78a897' # lch(65,20,170) 23 | -------------------------------------------------------------------------------- /style/rail-bridge-casing.yaml: -------------------------------------------------------------------------------- 1 | id: rail-bridge-casing 2 | source: spirit 3 | source-layer: railways 4 | type: line 5 | filter: 6 | - all 7 | - - in 8 | - [get, railway] 9 | - - literal 10 | - - rail 11 | - [get, bridge] 12 | paint: 13 | line-color: 14 | - case 15 | - [get, minor] 16 | - '#aaa' 17 | - '#5e5e5e' 18 | line-width: 19 | - interpolate 20 | - - exponential 21 | - 1.35 22 | - - zoom 23 | - 12 24 | - 3 25 | - 20 26 | - 14 27 | layout: 28 | line-cap: butt 29 | line-sort-key: 30 | - + 31 | - - case 32 | - [get, minor] 33 | - 0 34 | - 10 35 | - - '*' 36 | - [get, layer] 37 | - 1000 38 | -------------------------------------------------------------------------------- /style/rail-bridge-fill.yaml: -------------------------------------------------------------------------------- 1 | id: rail-bridge-fill 2 | source: spirit 3 | source-layer: railways 4 | type: line 5 | filter: 6 | - all 7 | - - in 8 | - [get, railway] 9 | - - literal 10 | - - rail 11 | - [get, bridge] 12 | paint: 13 | line-dasharray: [3, 3] 14 | line-color: white 15 | line-width: 16 | - interpolate 17 | - - exponential 18 | - 1.35 19 | - - zoom 20 | - 12 21 | - 1 22 | - 20 23 | - 8 24 | layout: 25 | line-cap: butt 26 | line-sort-key: 27 | - + 28 | - [get, z_order] 29 | - - '*' 30 | - [get, layer] 31 | - 1000 32 | -------------------------------------------------------------------------------- /style/rail-bridge-outer-casing.yaml: -------------------------------------------------------------------------------- 1 | id: rail-bridge-outer-casing 2 | source: spirit 3 | source-layer: railways 4 | type: line 5 | filter: 6 | - all 7 | - - in 8 | - [get, railway] 9 | - - literal 10 | - - rail 11 | - [get, bridge] 12 | paint: 13 | line-color: black 14 | line-width: 15 | - interpolate 16 | - - exponential 17 | - 1.35 18 | - - zoom 19 | - 12 20 | - 5 21 | - 20 22 | - 20 23 | layout: 24 | line-cap: butt 25 | line-sort-key: 26 | - + 27 | - - case 28 | - [get, minor] 29 | - 0 30 | - 10 31 | - - '*' 32 | - [get, layer] 33 | - 1000 34 | -------------------------------------------------------------------------------- /style/rail-casing.yaml: -------------------------------------------------------------------------------- 1 | id: rail-casing 2 | source: spirit 3 | source-layer: railways 4 | type: line 5 | filter: 6 | - all 7 | - - in 8 | - [get, railway] 9 | - - literal 10 | - - rail 11 | - ['!', [get, tunnel]] 12 | - ['!', [get, bridge]] 13 | paint: 14 | line-color: 15 | - case 16 | - [get, minor] 17 | - '#aaa' 18 | - '#5e5e5e' 19 | line-width: 20 | - interpolate 21 | - - exponential 22 | - 1.35 23 | - - zoom 24 | - 12 25 | - 3 26 | - 20 27 | - 14 28 | layout: 29 | line-cap: butt 30 | line-sort-key: 31 | - + 32 | - - case 33 | - [get, minor] 34 | - 0 35 | - 10 36 | - - '*' 37 | - [get, layer] 38 | - 1000 39 | -------------------------------------------------------------------------------- /style/rail-fill.yaml: -------------------------------------------------------------------------------- 1 | id: rail-fill 2 | source: spirit 3 | source-layer: railways 4 | type: line 5 | filter: 6 | - all 7 | - - in 8 | - [get, railway] 9 | - - literal 10 | - - rail 11 | - ['!', [get, tunnel]] 12 | - ['!', [get, bridge]] 13 | paint: 14 | line-dasharray: [3, 3] 15 | line-color: white 16 | line-width: 17 | - interpolate 18 | - - exponential 19 | - 1.35 20 | - - zoom 21 | - 12 22 | - 1 23 | - 20 24 | - 8 25 | layout: 26 | line-cap: butt 27 | line-sort-key: 28 | - + 29 | - [get, z_order] 30 | - - '*' 31 | - [get, layer] 32 | - 1000 33 | -------------------------------------------------------------------------------- /style/road-base-casing.yaml: -------------------------------------------------------------------------------- 1 | # A layer at the base of the transport stack, covering tunnels, as well as round end-caps for anything else 2 | id: road-base-casing 3 | source: spirit 4 | source-layer: roads 5 | type: line 6 | filter: 7 | # This messy filter could be done with a [step, [zoom], ..., N, ...] but this 8 | # would require re-stating the conditions for all lower zoom for each step. 9 | # Instead, it requires one of the any branches is met, and each branch involves 10 | # a zoom level conditional. If the zoom-level part is omitted, the results look 11 | # odd when zooming in and out. 12 | - any 13 | - - all # Allow certain classifications to appear when they are included in the source 14 | - - in 15 | - [get, highway] 16 | - - literal 17 | - - motorway 18 | - trunk 19 | - primary 20 | - secondary 21 | - tertiary 22 | - unclassified 23 | - residential 24 | - living_street 25 | - - all 26 | - [==, [get, highway], service] 27 | - [==, [get, minor], null] 28 | - [">=", [zoom], 14] 29 | - - all 30 | - [==, [get, highway], service] 31 | - [">=", [zoom], 15] 32 | - - all 33 | - - in 34 | - [get, highway] 35 | - - literal 36 | - - footway 37 | - cycleway 38 | - [">=", [zoom], 15] 39 | paint: 40 | line-color: 41 | - match 42 | - [get, highway] 43 | - motorway 44 | - '#b40017' # lch(35,80,30) 45 | - trunk 46 | - '#bd5c00' # lch(50,70,60) 47 | - primary 48 | - '#bd5c00' # lch(50,70,60) 49 | - secondary 50 | - '#c9952c' # lch(65,60,80) 51 | - tertiary 52 | - '#c9952c' # lch(65,60,80) 53 | - unclassified 54 | - '#b7bab4' # lch(75,3,130) 55 | - residential 56 | - '#b7bab4' # lch(75,3,130) 57 | - living_street 58 | - '#b7bab4' # lch(75,3,130) 59 | - service 60 | - '#b7bab4' # lch(75,3,130) 61 | - footway 62 | - '#696b67' # lch(75,3,130) 63 | - cycleway 64 | - '#696b67' # lch(75,3,130) 65 | - red 66 | line-width: 67 | - interpolate 68 | - - exponential 69 | - 1.35 70 | - - zoom 71 | - 12 72 | - - match 73 | - [get, highway] 74 | - motorway 75 | - 4 76 | - trunk 77 | - 3.5 78 | - primary 79 | - 3.5 80 | - secondary 81 | - 2 82 | - tertiary 83 | - 2 84 | - unclassified 85 | - 1.5 86 | - residential 87 | - 1.5 88 | - living_street 89 | - 1.5 90 | - service 91 | - - match 92 | - [to-string, [get, minor]] 93 | - "true" 94 | - 0.5 95 | - 1 96 | - footway 97 | - .2 98 | - cycleway 99 | - .2 100 | - 1.5 101 | - 20 102 | - - match 103 | - [get, highway] 104 | - motorway 105 | - 40 106 | - trunk 107 | - 30 108 | - primary 109 | - 30 110 | - secondary 111 | - 25 112 | - tertiary 113 | - 25 114 | - unclassified 115 | - 20 116 | - residential 117 | - 20 118 | - living_street 119 | - 20 120 | - service 121 | - - match 122 | - [to-string, [get, minor]] 123 | - "true" 124 | - 10 125 | - 14 126 | - footway 127 | - 4 128 | - cycleway 129 | - 4 130 | - 20 131 | layout: 132 | line-cap: round 133 | line-sort-key: 134 | - + 135 | - [get, z_order] 136 | - - '*' 137 | - [get, layer] 138 | - 1000 139 | -------------------------------------------------------------------------------- /style/road-base-fill.yaml: -------------------------------------------------------------------------------- 1 | # A layer at the base of the transport stack, covering tunnels, as well as round end-caps for anything else. 2 | id: road-base-fill 3 | source: spirit 4 | source-layer: roads 5 | type: line 6 | # This messy filter could be done with a [step, [zoom], ..., N, ...] but this 7 | # would require re-stating the conditions for all lower zoom for each step. 8 | # Instead, it requires one of the any branches is met, and each branch involves 9 | # a zoom level conditional. If the zoom-level part is omitted, the results look 10 | # odd when zooming in and out. 11 | filter: 12 | - any 13 | - - all # Allow certain classifications to appear when they are included in the source 14 | - - in 15 | - [get, highway] 16 | - - literal 17 | - - motorway 18 | - trunk 19 | - primary 20 | - secondary 21 | - tertiary 22 | - unclassified 23 | - residential 24 | - living_street 25 | - - all 26 | - [==, [get, highway], service] 27 | - [==, [get, minor], null] 28 | - [">=", [zoom], 14] 29 | - - all 30 | - [==, [get, highway], service] 31 | - [">=", [zoom], 15] 32 | paint: 33 | line-color: !!inc/file style/inc/road-fill-color.yaml 34 | line-width: 35 | - interpolate 36 | - - exponential 37 | - 1.35 38 | - - zoom 39 | - 12 40 | - - match 41 | - [get, highway] 42 | - motorway 43 | - 2 44 | - trunk 45 | - 1.5 46 | - primary 47 | - 1.5 48 | - secondary 49 | - 1 50 | - tertiary 51 | - 1 52 | - unclassified 53 | - 1 54 | - residential 55 | - 1 56 | - living_street 57 | - 1 58 | - service 59 | - - match 60 | - [to-string, [get, minor]] 61 | - "true" 62 | - 0.2 63 | - 0.5 64 | - 1 65 | - 20 66 | - - match 67 | - [get, highway] 68 | - motorway 69 | - 25 70 | - trunk 71 | - 20 72 | - primary 73 | - 20 74 | - secondary 75 | - 15 76 | - tertiary 77 | - 15 78 | - unclassified 79 | - 10 80 | - residential 81 | - 10 82 | - living_street 83 | - 10 84 | - service 85 | - - match 86 | - [to-string, [get, minor]] 87 | - "true" 88 | - 3 89 | - 6 90 | - 10 91 | 92 | layout: 93 | line-cap: round 94 | line-sort-key: 95 | - + 96 | - ['-', [get, z_order]] 97 | - - '*' 98 | - [get, layer] 99 | - -1000 100 | -------------------------------------------------------------------------------- /style/road-bridge-casing.yaml: -------------------------------------------------------------------------------- 1 | id: road-bridge-casing 2 | source: spirit 3 | source-layer: roads 4 | type: line 5 | filter: 6 | # This messy filter could be done with a [step, [zoom], ..., N, ...] but this 7 | # would require re-stating the conditions for all lower zoom for each step. 8 | # Instead, it requires one of the any branches is met, and each branch involves 9 | # a zoom level conditional. If the zoom-level part is omitted, the results look 10 | # odd when zooming in and out. 11 | - all 12 | - - any 13 | - - all # Allow certain classifications to appear when they are included in the source 14 | - - in 15 | - [get, highway] 16 | - - literal 17 | - - motorway 18 | - trunk 19 | - primary 20 | - secondary 21 | - tertiary 22 | - unclassified 23 | - residential 24 | - living_street 25 | - - all 26 | - [==, [get, highway], service] 27 | - [==, [get, minor], null] 28 | - [">=", [zoom], 14] 29 | - - all 30 | - [==, [get, highway], service] 31 | - [">=", [zoom], 15] 32 | - - all 33 | - - in 34 | - [get, highway] 35 | - - literal 36 | - - footway 37 | - cycleway 38 | - [">=", [zoom], 15] 39 | - [get, bridge] 40 | paint: 41 | line-color: black 42 | line-width: 43 | - interpolate 44 | - - exponential 45 | - 1.35 46 | - - zoom 47 | - 12 48 | - - match 49 | - [get, highway] 50 | - motorway 51 | - 4 52 | - trunk 53 | - 3.5 54 | - primary 55 | - 3.5 56 | - secondary 57 | - 2 58 | - tertiary 59 | - 2 60 | - unclassified 61 | - 1.5 62 | - residential 63 | - 1.5 64 | - living_street 65 | - 1.5 66 | - service 67 | - - match 68 | - [to-string, [get, minor]] 69 | - "true" 70 | - 0.5 71 | - 1 72 | - footway 73 | - .2 74 | - cycleway 75 | - .2 76 | - 1.5 77 | - 20 78 | - - match 79 | - [get, highway] 80 | - motorway 81 | - 40 82 | - trunk 83 | - 30 84 | - primary 85 | - 30 86 | - secondary 87 | - 25 88 | - tertiary 89 | - 25 90 | - unclassified 91 | - 20 92 | - residential 93 | - 20 94 | - living_street 95 | - 20 96 | - service 97 | - - match 98 | - [to-string, [get, minor]] 99 | - "true" 100 | - 10 101 | - 14 102 | - footway 103 | - 4 104 | - cycleway 105 | - 4 106 | - 20 107 | layout: 108 | line-cap: butt 109 | line-sort-key: 110 | - + 111 | - [get, z_order] 112 | - - '*' 113 | - [get, layer] 114 | - 1000 115 | -------------------------------------------------------------------------------- /style/road-bridge-fill.yaml: -------------------------------------------------------------------------------- 1 | id: road-bridge-fill 2 | source: spirit 3 | source-layer: roads 4 | type: line 5 | # This messy filter could be done with a [step, [zoom], ..., N, ...] but this 6 | # would require re-stating the conditions for all lower zoom for each step. 7 | # Instead, it requires one of the any branches is met, and each branch involves 8 | # a zoom level conditional. If the zoom-level part is omitted, the results look 9 | # odd when zooming in and out. 10 | filter: 11 | - all 12 | - - any 13 | - - all # Allow certain classifications to appear when they are included in the source 14 | - - in 15 | - [get, highway] 16 | - - literal 17 | - - motorway 18 | - trunk 19 | - primary 20 | - secondary 21 | - tertiary 22 | - unclassified 23 | - residential 24 | - living_street 25 | - - all 26 | - [==, [get, highway], service] 27 | - [==, [get, minor], null] 28 | - [">=", [zoom], 14] 29 | - - all 30 | - [==, [get, highway], service] 31 | - [">=", [zoom], 15] 32 | - [get, bridge] 33 | paint: 34 | line-color: !!inc/file style/inc/road-fill-color.yaml 35 | line-width: 36 | - interpolate 37 | - - exponential 38 | - 1.35 39 | - - zoom 40 | - 12 41 | - - match 42 | - [get, highway] 43 | - motorway 44 | - 2 45 | - trunk 46 | - 1.5 47 | - primary 48 | - 1.5 49 | - secondary 50 | - 1 51 | - tertiary 52 | - 1 53 | - unclassified 54 | - 1 55 | - residential 56 | - 1 57 | - living_street 58 | - 1 59 | - service 60 | - - match 61 | - [to-string, [get, minor]] 62 | - "true" 63 | - 0.2 64 | - 0.5 65 | - 1 66 | - 20 67 | - - match 68 | - [get, highway] 69 | - motorway 70 | - 25 71 | - trunk 72 | - 20 73 | - primary 74 | - 20 75 | - secondary 76 | - 15 77 | - tertiary 78 | - 15 79 | - unclassified 80 | - 10 81 | - residential 82 | - 10 83 | - living_street 84 | - 10 85 | - service 86 | - - match 87 | - [to-string, [get, minor]] 88 | - "true" 89 | - 3 90 | - 6 91 | - 10 92 | 93 | layout: 94 | line-cap: butt 95 | line-sort-key: 96 | - + 97 | - [get, z_order] 98 | - - '*' 99 | - [get, layer] 100 | - 1000 101 | -------------------------------------------------------------------------------- /style/road-casing.yaml: -------------------------------------------------------------------------------- 1 | id: road-casing 2 | source: spirit 3 | source-layer: roads 4 | type: line 5 | filter: 6 | # This messy filter could be done with a [step, [zoom], ..., N, ...] but this 7 | # would require re-stating the conditions for all lower zoom for each step. 8 | # Instead, it requires one of the any branches is met, and each branch involves 9 | # a zoom level conditional. If the zoom-level part is omitted, the results look 10 | # odd when zooming in and out. 11 | - all 12 | - - any 13 | - - all # Allow certain classifications to appear when they are included in the source 14 | - - in 15 | - [get, highway] 16 | - - literal 17 | - - motorway 18 | - trunk 19 | - primary 20 | - secondary 21 | - tertiary 22 | - unclassified 23 | - residential 24 | - living_street 25 | - - all 26 | - [==, [get, highway], service] 27 | - [==, [get, minor], null] 28 | - [">=", [zoom], 14] 29 | - - all 30 | - [==, [get, highway], service] 31 | - [">=", [zoom], 15] 32 | - - all 33 | - - in 34 | - [get, highway] 35 | - - literal 36 | - - footway 37 | - cycleway 38 | - [">=", [zoom], 15] 39 | - ['!', [get, tunnel]] 40 | - ['!', [get, bridge]] 41 | paint: 42 | line-color: !!inc/file style/inc/road-casing-color.yaml 43 | line-width: 44 | - interpolate 45 | - - exponential 46 | - 1.35 47 | - - zoom 48 | - 12 49 | - - match 50 | - [get, highway] 51 | - motorway 52 | - 4 53 | - trunk 54 | - 3.5 55 | - primary 56 | - 3.5 57 | - secondary 58 | - 2 59 | - tertiary 60 | - 2 61 | - unclassified 62 | - 1.5 63 | - residential 64 | - 1.5 65 | - living_street 66 | - 1.5 67 | - service 68 | - - match 69 | - [to-string, [get, minor]] 70 | - "true" 71 | - 0.5 72 | - 1 73 | - footway 74 | - .2 75 | - cycleway 76 | - .2 77 | - 1.5 78 | - 20 79 | - - match 80 | - [get, highway] 81 | - motorway 82 | - 40 83 | - trunk 84 | - 30 85 | - primary 86 | - 30 87 | - secondary 88 | - 25 89 | - tertiary 90 | - 25 91 | - unclassified 92 | - 20 93 | - residential 94 | - 20 95 | - living_street 96 | - 20 97 | - service 98 | - - match 99 | - [to-string, [get, minor]] 100 | - "true" 101 | - 10 102 | - 14 103 | - footway 104 | - 4 105 | - cycleway 106 | - 4 107 | - 20 108 | layout: 109 | line-cap: butt 110 | line-sort-key: 111 | - + 112 | - [get, z_order] 113 | - - '*' 114 | - [get, layer] 115 | - 1000 116 | -------------------------------------------------------------------------------- /style/road-fill.yaml: -------------------------------------------------------------------------------- 1 | id: road-fill 2 | source: spirit 3 | source-layer: roads 4 | type: line 5 | filter: 6 | # This messy filter could be done with a [step, [zoom], ..., N, ...] but this 7 | # would require re-stating the conditions for all lower zoom for each step. 8 | # Instead, it requires one of the any branches is met, and each branch involves 9 | # a zoom level conditional. If the zoom-level part is omitted, the results look 10 | # odd when zooming in and out. 11 | - all 12 | - - any 13 | - - all # Allow certain classifications to appear when they are included in the source 14 | - - in 15 | - [get, highway] 16 | - - literal 17 | - - motorway 18 | - trunk 19 | - primary 20 | - secondary 21 | - tertiary 22 | - unclassified 23 | - residential 24 | - living_street 25 | - - all 26 | - [==, [get, highway], service] 27 | - [==, [get, minor], null] 28 | - [">=", [zoom], 14] 29 | - - all 30 | - [==, [get, highway], service] 31 | - [">=", [zoom], 15] 32 | - ['!', [get, tunnel]] 33 | - ['!', [get, bridge]] 34 | paint: 35 | line-color: !!inc/file style/inc/road-fill-color.yaml 36 | line-width: 37 | - interpolate 38 | - - exponential 39 | - 1.35 40 | - - zoom 41 | - 12 42 | - - match 43 | - [get, highway] 44 | - motorway 45 | - 2 46 | - trunk 47 | - 1.5 48 | - primary 49 | - 1.5 50 | - secondary 51 | - 1 52 | - tertiary 53 | - 1 54 | - unclassified 55 | - 1 56 | - residential 57 | - 1 58 | - living_street 59 | - 1 60 | - service 61 | - - match 62 | - [to-string, [get, minor]] 63 | - "true" 64 | - 0.2 65 | - 0.5 66 | - 1 67 | - 20 68 | - - match 69 | - [get, highway] 70 | - motorway 71 | - 25 72 | - trunk 73 | - 20 74 | - primary 75 | - 20 76 | - secondary 77 | - 15 78 | - tertiary 79 | - 15 80 | - unclassified 81 | - 10 82 | - residential 83 | - 10 84 | - living_street 85 | - 10 86 | - service 87 | - - match 88 | - [to-string, [get, minor]] 89 | - "true" 90 | - 3 91 | - 6 92 | - 10 93 | 94 | layout: 95 | line-cap: butt 96 | line-sort-key: 97 | - + 98 | - [get, z_order] 99 | - - '*' 100 | - [get, layer] 101 | - 1000 102 | -------------------------------------------------------------------------------- /style/road-text.yaml: -------------------------------------------------------------------------------- 1 | id: road-text 2 | source: spirit 3 | source-layer: roads 4 | type: symbol 5 | filter: 6 | # This messy filter could be done with a [step, [zoom], ..., N, ...] but this 7 | # would require re-stating the conditions for all lower zoom for each step. 8 | # Instead, it requires one of the any branches is met, and each branch involves 9 | # a zoom level conditional. If the zoom-level part is omitted, the results look 10 | # odd when zooming in and out. 11 | - all 12 | - - any 13 | - - all # Allow certain classifications to appear when they are included in the source 14 | - - in 15 | - [get, highway] 16 | - - literal 17 | - - motorway 18 | - trunk 19 | - primary 20 | - secondary 21 | - tertiary 22 | - unclassified 23 | - residential 24 | - living_street 25 | - - all 26 | - [==, [get, highway], service] 27 | - [==, [get, minor], null] 28 | - [">=", [zoom], 14] 29 | - - all 30 | - [==, [get, highway], service] 31 | - [">=", [zoom], 15] 32 | - ['!', [get, tunnel]] 33 | - ['!', [get, bridge]] 34 | paint: 35 | text-color: black 36 | text-halo-color: !!inc/file style/inc/road-fill-color.yaml 37 | text-halo-width: 1.5 38 | text-halo-blur: 1 39 | layout: 40 | text-font: !!inc/file style/inc/regular-font.yaml 41 | text-field: '{name}' 42 | symbol-placement: line 43 | text-size: 44 | - interpolate 45 | - - exponential 46 | - 1.35 47 | - [zoom] 48 | - 12 49 | - 12 50 | - 20 51 | - 18 52 | symbol-spacing: 300 53 | -------------------------------------------------------------------------------- /style/road-tunnel-casing.yaml: -------------------------------------------------------------------------------- 1 | id: road-tunnel-casing 2 | source: spirit 3 | source-layer: roads 4 | type: line 5 | filter: 6 | - all 7 | - - any 8 | - - all # Allow certain classifications to appear when they are included in the source 9 | - - in 10 | - [get, highway] 11 | - - literal 12 | - - motorway 13 | - trunk 14 | - primary 15 | - secondary 16 | - tertiary 17 | - unclassified 18 | - residential 19 | - living_street 20 | - - all 21 | - [==, [get, highway], service] 22 | - [==, [get, minor], null] 23 | - [">=", [zoom], 14] 24 | - - all 25 | - [==, [get, highway], service] 26 | - [">=", [zoom], 15] 27 | - - all 28 | - - in 29 | - [get, highway] 30 | - - literal 31 | - - footway 32 | - cycleway 33 | - [">=", [zoom], 15] 34 | - [get, tunnel] 35 | paint: 36 | line-dasharray: [.5, .5] 37 | line-color: 38 | - match 39 | - [get, highway] 40 | - motorway 41 | - '#e19991' # 42 | - trunk 43 | - '#bd5c00' # lch(50,70,60) 44 | - primary 45 | - '#bd5c00' # lch(50,70,60) 46 | - secondary 47 | - '#c9952c' # lch(65,60,80) 48 | - tertiary 49 | - '#c9952c' # lch(65,60,80) 50 | - unclassified 51 | - '#b7bab4' # lch(75,3,130) 52 | - residential 53 | - '#b7bab4' # lch(75,3,130) 54 | - living_street 55 | - '#b7bab4' # lch(75,3,130) 56 | - service 57 | - '#b7bab4' # lch(75,3,130) 58 | - footway 59 | - '#696b67' # lch(75,3,130) 60 | - cycleway 61 | - '#696b67' # lch(75,3,130) 62 | - red 63 | line-width: 64 | - interpolate 65 | - - exponential 66 | - 1.35 67 | - - zoom 68 | - 12 69 | - - match 70 | - [get, highway] 71 | - motorway 72 | - 4 73 | - trunk 74 | - 3.5 75 | - primary 76 | - 3.5 77 | - secondary 78 | - 2 79 | - tertiary 80 | - 2 81 | - unclassified 82 | - 1.5 83 | - residential 84 | - 1.5 85 | - living_street 86 | - 1.5 87 | - service 88 | - - match 89 | - [to-string, [get, minor]] 90 | - "true" 91 | - 0.5 92 | - 1 93 | - footway 94 | - .2 95 | - cycleway 96 | - .2 97 | - 1.5 98 | - 20 99 | - - match 100 | - [get, highway] 101 | - motorway 102 | - 40 103 | - trunk 104 | - 30 105 | - primary 106 | - 30 107 | - secondary 108 | - 25 109 | - tertiary 110 | - 25 111 | - unclassified 112 | - 20 113 | - residential 114 | - 20 115 | - living_street 116 | - 20 117 | - service 118 | - - match 119 | - [to-string, [get, minor]] 120 | - "true" 121 | - 10 122 | - 14 123 | - footway 124 | - 4 125 | - cycleway 126 | - 4 127 | - 20 128 | layout: 129 | line-cap: butt 130 | line-sort-key: 131 | - + 132 | - [get, z_order] 133 | - - '*' 134 | - [get, layer] 135 | - 1000 136 | -------------------------------------------------------------------------------- /style/settlement-names.yaml: -------------------------------------------------------------------------------- 1 | id: settlement-names 2 | source: spirit 3 | source-layer: settlements 4 | type: symbol 5 | layout: 6 | text-field: '{name}' 7 | text-font: !!inc/file style/inc/regular-font.yaml 8 | text-size: 9 | - match 10 | - [get, place] 11 | - city 12 | - 14 13 | - 12 14 | paint: 15 | text-color: black 16 | text-halo-width: 1 17 | text-halo-color: white 18 | -------------------------------------------------------------------------------- /style/transit-points.yaml: -------------------------------------------------------------------------------- 1 | id: transit-points 2 | source: spirit 3 | source-layer: transit-points 4 | type: symbol 5 | layout: 6 | icon-image: 7 | - match 8 | - [get, mode] 9 | - bus 10 | - - case 11 | - - get 12 | - station 13 | - bus_station 14 | - bus_stop 15 | - tram 16 | - bus_stop 17 | - subway 18 | - subway 19 | - train 20 | - train 21 | - airplane 22 | - airport 23 | - wetland 24 | icon-allow-overlap: true 25 | text-optional: true 26 | text-field: '{name}' 27 | text-font: !!inc/file style/inc/regular-font.yaml 28 | text-size: 12 29 | text-offset: [0, 0.4] 30 | text-anchor: top 31 | paint: 32 | text-color: '#003c77' 33 | text-halo-width: 1 34 | text-halo-color: white 35 | -------------------------------------------------------------------------------- /style/vegetation-names.yaml: -------------------------------------------------------------------------------- 1 | id: vegetation-names 2 | source: spirit 3 | source-layer: vegetation-names 4 | minzoom: 13 5 | type: symbol 6 | filter: 7 | - '>=' 8 | - [get, way_area] 9 | - ['*', 1500, 6126430366.1, ['^', 0.25, [zoom]]] 10 | layout: 11 | text-field: '{name}' 12 | text-font: !!inc/file style/inc/regular-font.yaml 13 | text-size: 12 14 | text-max-width: 15 | - interpolate 16 | - [linear] 17 | - [length, [get, name]] 18 | - 20 19 | - 7 20 | - 40 21 | - 10 22 | - 60 23 | - 15 24 | paint: 25 | text-color: black 26 | text-halo-width: 1 27 | text-halo-color: 28 | - match 29 | - [get, vegetation] 30 | - wood 31 | - '#56b155' # lch(65,60,140) 32 | - heath 33 | - '#9cc47f' # lch(75,40,130) 34 | - scrub 35 | - '#9cc47f' # lch(75,40,130) 36 | - grass 37 | - '#d5e9c5' # lch(90,20,130) 38 | - wetland 39 | - '#d5e9c5' 40 | - red 41 | -------------------------------------------------------------------------------- /style/vegetation-pattern.yaml: -------------------------------------------------------------------------------- 1 | # The pattern selection for this layer has to align with 2 | 3 | id: vegetation-pattern 4 | source: spirit 5 | source-layer: vegetation 6 | type: fill 7 | filter: ["==", ["get", "vegetation"], "wetland"] 8 | paint: 9 | fill-pattern: wetland 10 | -------------------------------------------------------------------------------- /style/vegetation.yaml: -------------------------------------------------------------------------------- 1 | id: vegetation 2 | source: spirit 3 | source-layer: vegetation 4 | type: fill 5 | paint: 6 | fill-color: 7 | - match 8 | - [get, vegetation] 9 | - wood 10 | - '#56b155' # lch(65,60,140) 11 | - heath 12 | - '#9cc47f' # lch(75,40,130) 13 | - scrub 14 | - '#9cc47f' # lch(75,40,130) 15 | - grass 16 | - '#d5e9c5' # lch(90,20,130) 17 | - wetland 18 | - '#d5e9c5' 19 | - red 20 | -------------------------------------------------------------------------------- /style/water-line-text.yaml: -------------------------------------------------------------------------------- 1 | id: water-line-text 2 | source: spirit 3 | source-layer: water-lines 4 | type: symbol 5 | paint: 6 | text-color: '#24586d' # lch(35,20,240) 7 | text-halo-color: '#e7f2f9' # lch(95,5,240) 8 | text-halo-width: 1.5 9 | text-halo-blur: 1 10 | minzoom: 12 11 | layout: 12 | text-font: !!inc/file style/inc/regular-font.yaml 13 | text-field: '{name}' 14 | symbol-placement: line 15 | text-size: 16 | - interpolate 17 | - - exponential 18 | - 1.35 19 | - - zoom 20 | - 12 21 | - - match 22 | - - get 23 | - waterway 24 | - river 25 | - 10 26 | - canal 27 | - 10 28 | - 0 29 | - 15 30 | - - match 31 | - - get 32 | - waterway 33 | - river 34 | - 14 35 | - canal 36 | - 14 37 | - stream 38 | - 10 39 | - 0 40 | - 18 41 | - - match 42 | - - get 43 | - waterway 44 | - river 45 | - 18 46 | - canal 47 | - 18 48 | - stream 49 | - 14 50 | - 0 51 | symbol-spacing: 400 52 | text-letter-spacing: 0.2 53 | filter: 54 | - step 55 | - - zoom 56 | - - any 57 | - - '==' 58 | - [get, waterway] 59 | - river 60 | - 14 61 | - - any 62 | - - '==' 63 | - [get, waterway] 64 | - river 65 | - - '==' 66 | - [get, waterway] 67 | - canal 68 | - 15 69 | - - any 70 | - - '==' 71 | - [get, waterway] 72 | - river 73 | - - '==' 74 | - [get, waterway] 75 | - canal 76 | - - '==' 77 | - [get, waterway] 78 | - stream 79 | -------------------------------------------------------------------------------- /style/water-lines.yaml: -------------------------------------------------------------------------------- 1 | id: water-lines 2 | source: spirit 3 | source-layer: water-lines 4 | type: line 5 | paint: 6 | line-color: '#4cb7e1' #lch(70,35,240) 7 | line-width: 8 | - interpolate 9 | - - exponential 10 | - 1.35 11 | - - zoom 12 | - 8 13 | - - match 14 | - [get, waterway] 15 | - river 16 | - 0.2 17 | - canal 18 | - 0.2 19 | - 0 20 | - 14 21 | - - match 22 | - [get, waterway] 23 | - river 24 | - 2 25 | - canal 26 | - 2 27 | - stream 28 | - 0.8 29 | - drain 30 | - 0.1 31 | - ditch 32 | - 0.1 33 | - 0 34 | - 20 35 | - - match 36 | - [get, waterway] 37 | - river 38 | - 10 39 | - canal 40 | - 10 41 | - stream 42 | - 6 43 | - drain 44 | - 3 45 | - ditch 46 | - 3 47 | - 0 48 | -------------------------------------------------------------------------------- /style/water-names.yaml: -------------------------------------------------------------------------------- 1 | id: water-names 2 | source: spirit 3 | source-layer: water-names 4 | type: symbol 5 | paint: 6 | text-color: '#24586d' # lch(35,20,240) 7 | text-halo-color: '#e7f2f9' # lch(95,5,240) 8 | text-halo-width: 1.5 9 | text-halo-blur: 1 10 | layout: 11 | text-font: !!inc/file style/inc/regular-font.yaml 12 | text-field: '{name}' 13 | symbol-placement: point 14 | text-size: 12 15 | filter: 16 | - '>=' 17 | - [get, way_area] 18 | - ['*', 750, 6126430366.1, ['^', 0.25, [zoom]]] 19 | -------------------------------------------------------------------------------- /style/water.yaml: -------------------------------------------------------------------------------- 1 | id: water 2 | source: spirit 3 | source-layer: water 4 | type: fill 5 | paint: 6 | fill-color: '#4cb7e1' #lch(70,35,240) 7 | -------------------------------------------------------------------------------- /themes/shortbread/init.lua: -------------------------------------------------------------------------------- 1 | -- --------------------------------------------------------------------------- 2 | -- 3 | -- Theme: shortbread 4 | -- 5 | -- --------------------------------------------------------------------------- 6 | 7 | local theme = {} 8 | 9 | return theme 10 | 11 | -- --------------------------------------------------------------------------- 12 | -------------------------------------------------------------------------------- /themes/shortbread/topics/addresses.lua: -------------------------------------------------------------------------------- 1 | -- --------------------------------------------------------------------------- 2 | -- 3 | -- Theme: shortbread_v1 4 | -- Topic: addresses 5 | -- 6 | -- --------------------------------------------------------------------------- 7 | 8 | local themepark, theme, cfg = ... 9 | local expire = require('expire') 10 | 11 | themepark:add_table{ 12 | name = 'addresses', 13 | ids_type = 'any', 14 | geom = 'point', 15 | columns = themepark:columns({ 16 | { column = 'housename', type = 'text' }, 17 | { column = 'housenumber', type = 'text' }, 18 | }), 19 | tags = { 20 | { key = 'addr:housename' }, 21 | { key = 'addr:housenumber' }, 22 | }, 23 | tiles = { 24 | minzoom = 14, 25 | }, 26 | expire = expire.shortbread(14, 14, 'addresses', 'full-area') 27 | } 28 | 29 | -- --------------------------------------------------------------------------- 30 | 31 | local function process(t) 32 | if not t['addr:housenumber'] and not t['addr:housename'] then 33 | return nil 34 | end 35 | 36 | return { 37 | housenumber = t['addr:housenumber'], 38 | housename = t['addr:housename'], 39 | } 40 | end 41 | 42 | -- --------------------------------------------------------------------------- 43 | 44 | themepark:add_proc('node', function(object, data) 45 | -- Shortbread spec: Ignore addresses that are already in "pois" layer. 46 | if data.shortbread_in_pois then 47 | return 48 | end 49 | 50 | local a = process(object.tags) 51 | if a then 52 | a.geom = object:as_point() 53 | themepark:insert('addresses', a, object.tags) 54 | end 55 | end) 56 | 57 | themepark:add_proc('way', function(object, data) 58 | -- Shortbread spec: Ignore addresses that are already in "pois" layer. 59 | if data.shortbread_in_pois or not object.is_closed then 60 | return 61 | end 62 | 63 | local a = process(object.tags) 64 | if a then 65 | a.geom = object:as_polygon():pole_of_inaccessibility() 66 | themepark:insert('addresses', a, object.tags) 67 | end 68 | end) 69 | 70 | themepark:add_proc('relation', function(object, data) 71 | -- Shortbread spec: Ignore addresses that are already in "pois" layer. 72 | if data.shortbread_in_pois then 73 | return 74 | end 75 | 76 | local a = process(object.tags) 77 | if a then 78 | for sgeom in object:as_multipolygon():geometries() do 79 | a.geom = sgeom:pole_of_inaccessibility() 80 | themepark:insert('addresses', a, object.tags) 81 | end 82 | end 83 | end) 84 | 85 | -- --------------------------------------------------------------------------- 86 | -------------------------------------------------------------------------------- /themes/shortbread/topics/aerialways.lua: -------------------------------------------------------------------------------- 1 | -- --------------------------------------------------------------------------- 2 | -- 3 | -- Theme: shortbread_v1 4 | -- Topic: aerialways 5 | -- 6 | -- --------------------------------------------------------------------------- 7 | 8 | local themepark, theme, cfg = ... 9 | local expire = require('expire') 10 | local aerialway_values = { 'cable_car', 'gondola', 'goods', 'chair_lift', 11 | 'drag_lift', 't-bar', 'j-bar', 'platter', 'rope_tow' } 12 | 13 | local tags = {} 14 | 15 | for _, value in ipairs(aerialway_values) do 16 | table.insert(tags, { key = 'aerialway', value = value, on = 'w' }) 17 | end 18 | 19 | themepark:add_table{ 20 | name = 'aerialways', 21 | ids_type = 'way', 22 | geom = 'linestring', 23 | columns = themepark:columns({ 24 | { column = 'kind', type = 'text', not_null = true }, 25 | }), 26 | tags = tags, 27 | tiles = { 28 | minzoom = 12, 29 | }, 30 | expire = expire.shortbread(12, 14, 'aerialways', 'full-area') 31 | } 32 | 33 | -- --------------------------------------------------------------------------- 34 | 35 | local get_aerialway_value = osm2pgsql.make_check_values_func(aerialway_values) 36 | 37 | -- --------------------------------------------------------------------------- 38 | 39 | themepark:add_proc('way', function(object, data) 40 | local t = object.tags 41 | 42 | local aerialway = get_aerialway_value(t.aerialway) 43 | if aerialway then 44 | local a = { 45 | kind = aerialway, 46 | geom = object:as_linestring() 47 | } 48 | 49 | themepark:insert('aerialways', a, t) 50 | end 51 | end) 52 | 53 | -- --------------------------------------------------------------------------- 54 | -------------------------------------------------------------------------------- /themes/shortbread/topics/boundaries.lua: -------------------------------------------------------------------------------- 1 | -- --------------------------------------------------------------------------- 2 | -- 3 | -- Theme: shortbread_v1 4 | -- Topic: boundaries 5 | -- 6 | -- --------------------------------------------------------------------------- 7 | 8 | local themepark, theme, cfg = ... 9 | local expire = require('expire') 10 | 11 | themepark:add_table{ 12 | name = 'boundaries', 13 | ids_type = 'way', 14 | geom = 'linestring', 15 | columns = themepark:columns({ 16 | { column = 'admin_level', type = 'int', not_null = true }, 17 | { column = 'maritime', type = 'bool' }, 18 | { column = 'disputed', type = 'bool' }, 19 | }), 20 | tags = { 21 | { key = 'admin_level', values = { '2', '4' }, on = 'r' }, 22 | { key = 'boundary', values = { 'administrative', 'disputed' }, on = 'r' }, 23 | { key = 'disputed', value = 'yes', on = 'w' }, 24 | { key = 'maritime', value = 'yes', on = 'w' }, 25 | { key = 'natural', value = 'coastline', on = 'w' }, 26 | { key = 'type', value = 'boundary', on = 'r' }, 27 | }, 28 | tiles = { 29 | minzoom = 2 30 | }, 31 | expire = expire.shortbread(0, 14, 'boundaries', 'boundary-only') 32 | } 33 | 34 | local rinfos = {} 35 | 36 | -- --------------------------------------------------------------------------- 37 | 38 | -- Check the (string) admin level. Change this depending on which admin 39 | -- levels you want to process. Shortbread only shows 2 and 4. 40 | -- valid values must work with tonumber! 41 | local function valid_admin_level(level) 42 | return level == '2' or level == '4' 43 | end 44 | 45 | -- Check if this looks like a boundary and return admin_level as number 46 | -- Return nil if this is not a valid administrative boundary. 47 | local function get_admin_level(tags) 48 | local type = tags.type 49 | 50 | if type == 'boundary' or type == 'multipolygon' then 51 | local boundary = tags.boundary 52 | if boundary == 'administrative' and valid_admin_level(tags.admin_level) then 53 | return tonumber(tags.admin_level) 54 | end 55 | end 56 | end 57 | 58 | 59 | local function valid_disputed(tags) 60 | local type = tags.type 61 | return (type == 'boundary' or type == 'multipolygon') and tags.boundary == 'disputed' 62 | end 63 | 64 | -- --------------------------------------------------------------------------- 65 | 66 | themepark:add_proc('way', function(object, data) 67 | if osm2pgsql.stage == 1 then 68 | return 69 | end 70 | 71 | local info = rinfos[object.id] 72 | if not info then 73 | return 74 | end 75 | 76 | local t = object.tags 77 | if not info.admin_level then 78 | return 79 | end 80 | local a = { 81 | admin_level = info.admin_level, 82 | maritime = (t.maritime and (t.maritime == 'yes' or t.natural == 'coastline')), 83 | disputed = info.disputed or (t.disputed and t.disputed == 'yes'), 84 | geom = object:as_linestring() 85 | } 86 | themepark.themes.core.add_name(a, object) 87 | themepark:insert('boundaries', a, t) 88 | end) 89 | 90 | themepark:add_proc('select_relation_members', function(relation) 91 | -- It isn't necessary to process boundary=disputed relations separately because 92 | -- if they have an admin_level from another relation they will get added anyways. 93 | if valid_admin_level(relation.tags.admin_level) then 94 | return { ways = osm2pgsql.way_member_ids(relation) } 95 | end 96 | end) 97 | 98 | themepark:add_proc('relation', function(object, data) 99 | local t = object.tags 100 | 101 | local admin_level = get_admin_level(t) 102 | local disputed = valid_disputed(t) 103 | -- If a relation is not an admin boundary or disputed boundary it has 104 | -- nothing to tell us and we don't need the ways. 105 | if not admin_level and not disputed then 106 | return 107 | end 108 | 109 | for _, member in ipairs(object.members) do 110 | if member.type == 'w' then 111 | if not rinfos[member.ref] then 112 | rinfos[member.ref] = { disputed = false } 113 | end 114 | if admin_level ~= nil and 115 | (rinfos[member.ref].admin_level == nil or rinfos[member.ref].admin_level > admin_level) then 116 | rinfos[member.ref].admin_level = admin_level 117 | end 118 | rinfos[member.ref].disputed = disputed or rinfos[member.ref].disputed 119 | end 120 | end 121 | end) 122 | 123 | -- --------------------------------------------------------------------------- 124 | -------------------------------------------------------------------------------- /themes/shortbread/topics/boundary_labels.lua: -------------------------------------------------------------------------------- 1 | -- --------------------------------------------------------------------------- 2 | -- 3 | -- Theme: shortbread_v1 4 | -- Topic: boundaries 5 | -- 6 | -- --------------------------------------------------------------------------- 7 | 8 | local themepark, theme, cfg = ... 9 | local expire = require('expire') 10 | 11 | themepark:add_table{ 12 | name = 'boundary_labels', 13 | ids_type = 'relation', 14 | geom = 'point', 15 | columns = themepark:columns('core/name', { 16 | { column = 'admin_level', type = 'int' }, 17 | { column = 'way_area', type = 'real' }, 18 | { column = 'minzoom', type = 'int', tiles = 'minzoom' }, 19 | }), 20 | tags = { 21 | { key = 'admin_level', values = { '2', '4' }, on = 'r' }, 22 | { key = 'boundary', value = 'administrative', on = 'r' }, 23 | }, 24 | expire = expire.shortbread(0, 14, 'boundary_labels', 'full-area') 25 | } 26 | 27 | -- --------------------------------------------------------------------------- 28 | 29 | themepark:add_proc('relation', function(object, data) 30 | local t = object.tags 31 | if t.boundary == 'administrative' then 32 | local admin_level = tonumber(t.admin_level) 33 | if admin_level == nil or admin_level > 4 or admin_level < 2 or admin_level == 3 then 34 | return 35 | end 36 | 37 | local mgeom = object:as_multipolygon() 38 | 39 | if mgeom then 40 | local a = { admin_level = admin_level } 41 | 42 | themepark.themes.core.add_name(a, object) 43 | 44 | local best_geom 45 | local best_area = 0 46 | for sgeom in mgeom:geometries() do 47 | local this_area = sgeom:spherical_area() 48 | if this_area > best_area then 49 | best_area = this_area 50 | best_geom = sgeom 51 | end 52 | end 53 | 54 | if best_geom then 55 | a.way_area = best_area 56 | 57 | if admin_level == 2 then 58 | if a.way_area > 2000000 then 59 | a.minzoom = 2 60 | elseif a.way_area > 700000 then 61 | a.minzoom = 3 62 | elseif a.way_area > 100000 then 63 | a.minzoom = 4 64 | else 65 | a.minzoom = 5 66 | end 67 | else -- == 4 68 | if a.way_area > 700000 then 69 | a.minzoom = 3 70 | elseif a.way_area > 100000 then 71 | a.minzoom = 4 72 | else 73 | a.minzoom = 5 74 | end 75 | end 76 | 77 | a.geom = best_geom:transform(3857):pole_of_inaccessibility() 78 | themepark:insert('boundary_labels', a, t) 79 | end 80 | end 81 | end 82 | end) 83 | 84 | -- --------------------------------------------------------------------------- 85 | -------------------------------------------------------------------------------- /themes/shortbread/topics/bridges.lua: -------------------------------------------------------------------------------- 1 | -- --------------------------------------------------------------------------- 2 | -- 3 | -- Theme: shortbread_v1 4 | -- Topic: bridges 5 | -- 6 | -- --------------------------------------------------------------------------- 7 | 8 | local themepark, theme, cfg = ... 9 | local expire = require('expire') 10 | 11 | themepark:add_table{ 12 | name = 'bridges', 13 | ids_type = 'area', 14 | geom = 'polygon', 15 | columns = themepark:columns({ 16 | { column = 'kind', type = 'text', not_null = true }, 17 | { column = 'layer', type = 'int2', tiles = false }, 18 | }), 19 | tags = { 20 | { key = 'man_made', value = 'bridge', on = 'a' }, 21 | }, 22 | tiles = { 23 | minzoom = 12, 24 | }, 25 | expire = expire.shortbread(12, 14, 'bridges', 'full-area') 26 | } 27 | 28 | -- --------------------------------------------------------------------------- 29 | 30 | themepark:add_proc('area', function(object, data) 31 | local t = object.tags 32 | 33 | if t.man_made == 'bridge' then 34 | local a = { 35 | kind = 'bridge', 36 | layer = data.core.layer 37 | } 38 | 39 | for sgeom in object:as_area():geometries() do 40 | a.geom = sgeom 41 | themepark:insert('bridges', a, t) 42 | end 43 | end 44 | end) 45 | 46 | -- --------------------------------------------------------------------------- 47 | -------------------------------------------------------------------------------- /themes/shortbread/topics/dams.lua: -------------------------------------------------------------------------------- 1 | -- --------------------------------------------------------------------------- 2 | -- 3 | -- Theme: shortbread_v1 4 | -- Topic: dams 5 | -- 6 | -- --------------------------------------------------------------------------- 7 | 8 | local themepark, theme, cfg = ... 9 | local expire = require('expire') 10 | 11 | themepark:add_table{ 12 | name = 'dam_lines', 13 | ids_type = 'way', 14 | geom = 'linestring', 15 | columns = themepark:columns({ 16 | { column = 'kind', type = 'text', not_null = true }, 17 | }), 18 | tags = { 19 | { key = 'waterway', value = 'dam', on = 'w' }, 20 | }, 21 | tiles = { 22 | minzoom = 12, 23 | }, 24 | expire = expire.shortbread(14, 14, 'dam_lines', 'full-area') 25 | } 26 | 27 | themepark:add_table{ 28 | name = 'dam_polygons', 29 | ids_type = 'way', 30 | geom = 'polygon', 31 | columns = themepark:columns({ 32 | { column = 'kind', type = 'text', not_null = true }, 33 | }), 34 | tags = { 35 | { key = 'waterway', value = 'dam', on = 'a' }, 36 | }, 37 | tiles = { 38 | minzoom = 12, 39 | }, 40 | expire = expire.shortbread(12, 14, 'dam_polygons', 'full-area') 41 | } 42 | 43 | -- --------------------------------------------------------------------------- 44 | 45 | themepark:add_proc('way', function(object, data) 46 | if object.is_closed then 47 | return 48 | end 49 | 50 | local t = object.tags 51 | local waterway = t.waterway 52 | 53 | if waterway == 'dam' then 54 | local a = { kind = waterway } 55 | a.geom = object:as_linestring() 56 | themepark:insert('dam_lines', a, t) 57 | end 58 | end) 59 | 60 | themepark:add_proc('area', function(object, data) 61 | local t = object.tags 62 | local waterway = t.waterway 63 | 64 | if waterway == 'dam' then 65 | local a = { kind = waterway } 66 | 67 | for sgeom in object:as_area():geometries() do 68 | a.geom = sgeom 69 | themepark:insert('dam_polygons', a, t) 70 | end 71 | end 72 | end) 73 | 74 | -- --------------------------------------------------------------------------- 75 | -------------------------------------------------------------------------------- /themes/shortbread/topics/ferries.lua: -------------------------------------------------------------------------------- 1 | -- --------------------------------------------------------------------------- 2 | -- 3 | -- Theme: shortbread_v1 4 | -- Topic: ferries 5 | -- 6 | -- --------------------------------------------------------------------------- 7 | 8 | local themepark, theme, cfg = ... 9 | local expire = require('expire') 10 | 11 | themepark:add_table{ 12 | name = 'ferries', 13 | ids_type = 'way', 14 | geom = 'linestring', 15 | columns = themepark:columns('core/name', { 16 | { column = 'kind', type = 'text', not_null = true }, 17 | { column = 'minzoom', type = 'int', tiles = 'minzoom' }, 18 | }), 19 | tags = { 20 | { key = 'route', value = 'ferry', on = 'w' }, 21 | { key = 'motor_vehicle', on = 'w' }, 22 | }, 23 | tiles = { 24 | minzoom = 10, 25 | }, 26 | expire = expire.shortbread(10, 14, 'ferries', 'full-area') 27 | } 28 | 29 | -- --------------------------------------------------------------------------- 30 | 31 | themepark:add_proc('way', function(object, data) 32 | local t = object.tags 33 | 34 | if t.route == 'ferry' then 35 | local a = { 36 | kind = 'ferry', 37 | geom = object:as_linestring() 38 | } 39 | 40 | if t.motor_vehicle and t.motor_vehicle ~= 'no' then 41 | a.minzoom = 10 42 | else 43 | a.minzoom = 12 44 | end 45 | 46 | themepark.themes.core.add_name(a, object) 47 | themepark:insert('ferries', a, t) 48 | end 49 | end) 50 | 51 | -- --------------------------------------------------------------------------- 52 | -------------------------------------------------------------------------------- /themes/shortbread/topics/land.lua: -------------------------------------------------------------------------------- 1 | -- --------------------------------------------------------------------------- 2 | -- 3 | -- Theme: shortbread_v1 4 | -- Topic: land 5 | -- 6 | -- --------------------------------------------------------------------------- 7 | 8 | local themepark, theme, cfg = ... 9 | local expire = require('expire') 10 | 11 | -- --------------------------------------------------------------------------- 12 | 13 | local landuse_lookup = { 14 | forest = 7, 15 | grass = 11, 16 | meadow = 11, 17 | orchard = 11, 18 | vineyard = 11, 19 | allotments = 11, 20 | cemetery = 13, 21 | 22 | village_green = 11, 23 | recreation_ground = 11, 24 | greenhouse_horticulture = 11, 25 | plant_nursery = 11, 26 | 27 | residential = 10, 28 | industrial = 10, 29 | commercial = 10, 30 | garages = 10, 31 | retail = 10, 32 | railway = 10, 33 | landfill = 10, 34 | 35 | quarry = 11, 36 | 37 | brownfield = 10, 38 | greenfield = 10, 39 | farmyard = 10, 40 | farmland = 10, 41 | } 42 | 43 | local natural_lookup = { 44 | sand = 10, 45 | beach = 10, 46 | heath = 11, 47 | scrub = 11, 48 | grassland = 11, 49 | bare_rock = 11, 50 | scree = 11, 51 | shingle = 11, 52 | } 53 | 54 | local wetland_values = { "swamp", "bog", "string_bog", "wet_meadow", "marsh" } 55 | 56 | local leisure_values = { "golf_course", "park", "garden", "playground", "miniature_golf" } 57 | 58 | -- --------------------------------------------------------------------------- 59 | 60 | local landuse_values = {} 61 | for k, _ in pairs(landuse_lookup) do 62 | table.insert(landuse_values, k) 63 | end 64 | 65 | local natural_values = {} 66 | for k, _ in pairs(natural_lookup) do 67 | table.insert(natural_values, k) 68 | end 69 | 70 | local check_wetland = osm2pgsql.make_check_values_func(wetland_values) 71 | 72 | local check_leisure = osm2pgsql.make_check_values_func(leisure_values) 73 | 74 | -- --------------------------------------------------------------------------- 75 | 76 | themepark:add_table{ 77 | name = 'land', 78 | ids_type = 'area', 79 | geom = 'geometry', 80 | columns = themepark:columns({ 81 | { column = 'kind', type = 'text', not_null = true }, 82 | { column = 'minzoom', type = 'int', not_null = true, tiles = 'minzoom' }, 83 | }), 84 | tags = { 85 | { key = 'landuse', values = landuse_values, on = 'a' }, 86 | { key = 'leisure', values = leisure_values, on = 'a' }, 87 | { key = 'natural', values = natural_values, on = 'a' }, 88 | { key = 'wetland', values = wetland_values, on = 'a' }, 89 | }, 90 | tiles = { 91 | minzoom = 7 92 | }, 93 | expire = expire.shortbread(7, 14, 'land', 'full-area') 94 | } 95 | 96 | -- --------------------------------------------------------------------------- 97 | 98 | themepark:add_proc('area', function(object, data) 99 | local t = object.tags 100 | local a = { geom = object:as_area() } 101 | 102 | local minzoom = landuse_lookup[t.landuse] 103 | if minzoom then 104 | a.kind = t.landuse 105 | a.minzoom = minzoom 106 | elseif t.natural == 'wood' then 107 | a.kind = 'forest' 108 | a.minzoom = 7 109 | elseif t.amenity == 'grave_yard' then 110 | a.kind = 'grave_yard' 111 | a.minzoom = 13 112 | else 113 | minzoom = natural_lookup[t.natural] 114 | if minzoom then 115 | a.kind = t.natural 116 | a.minzoom = minzoom 117 | else 118 | local wetland = check_wetland(t.wetland) 119 | if wetland then 120 | a.kind = wetland 121 | a.minzoom = 11 122 | else 123 | local leisure = check_leisure(t.leisure) 124 | if leisure then 125 | a.kind = leisure 126 | a.minzoom = 11 127 | end 128 | end 129 | end 130 | end 131 | 132 | if a.kind then 133 | themepark:insert('land', a, t) 134 | end 135 | end) 136 | 137 | -- --------------------------------------------------------------------------- 138 | -------------------------------------------------------------------------------- /themes/shortbread/topics/piers.lua: -------------------------------------------------------------------------------- 1 | -- --------------------------------------------------------------------------- 2 | -- 3 | -- Theme: shortbread_v1 4 | -- Topic: piers 5 | -- 6 | -- --------------------------------------------------------------------------- 7 | 8 | local themepark, theme, cfg = ... 9 | local expire = require('expire') 10 | 11 | local man_made_values = { 'pier', 'breakwater', 'groyne' } 12 | 13 | themepark:add_table{ 14 | name = 'pier_lines', 15 | ids_type = 'way', 16 | geom = 'linestring', 17 | columns = themepark:columns({ 18 | { column = 'kind', type = 'text', not_null = true }, 19 | }), 20 | tags = { 21 | { key = 'man_made', values = man_made_values, on = 'w' }, 22 | }, 23 | tiles = { 24 | minzoom = 12, 25 | }, 26 | expire = expire.shortbread(12, 14, 'pier_lines', 'full-area') 27 | } 28 | 29 | themepark:add_table{ 30 | name = 'pier_polygons', 31 | ids_type = 'way', 32 | geom = 'polygon', 33 | columns = themepark:columns({ 34 | { column = 'kind', type = 'text', not_null = true }, 35 | }), 36 | tags = { 37 | { key = 'man_made', values = man_made_values, on = 'a' }, 38 | }, 39 | tiles = { 40 | minzoom = 12, 41 | }, 42 | expire = expire.shortbread(12, 14, 'pier_polygons', 'full-area') 43 | } 44 | 45 | -- --------------------------------------------------------------------------- 46 | 47 | themepark:add_proc('way', function(object, data) 48 | if object.is_closed then 49 | return 50 | end 51 | 52 | local t = object.tags 53 | local man_made = t.man_made 54 | 55 | if man_made == 'pier' or man_made == 'breakwater' or man_made == 'groyne' then 56 | local a = { kind = man_made } 57 | a.geom = object:as_linestring() 58 | themepark:insert('pier_lines', a, t) 59 | end 60 | end) 61 | 62 | themepark:add_proc('area', function(object, data) 63 | local t = object.tags 64 | local man_made = t.man_made 65 | 66 | if man_made == 'pier' or man_made == 'breakwater' or man_made == 'groyne' then 67 | local a = { kind = man_made } 68 | 69 | for sgeom in object:as_area():geometries() do 70 | a.geom = sgeom 71 | themepark:insert('pier_polygons', a, t) 72 | end 73 | end 74 | end) 75 | 76 | -- --------------------------------------------------------------------------- 77 | -------------------------------------------------------------------------------- /themes/shortbread/topics/places.lua: -------------------------------------------------------------------------------- 1 | -- --------------------------------------------------------------------------- 2 | -- 3 | -- Theme: shortbread_v1 4 | -- Topic: places 5 | -- 6 | -- --------------------------------------------------------------------------- 7 | 8 | local themepark, theme, cfg = ... 9 | local expire = require('expire') 10 | 11 | -- --------------------------------------------------------------------------- 12 | 13 | local place_types = { 14 | city = { pop = 100000, minzoom = 6 }, 15 | town = { pop = 5000, minzoom = 7 }, 16 | village = { pop = 100, minzoom = 10 }, 17 | hamlet = { pop = 10, minzoom = 10 }, 18 | suburb = { pop = 1000, minzoom = 10 }, 19 | quarter = { pop = 500, minzoom = 10 }, 20 | neighborhood = { pop = 100, minzoom = 10 }, 21 | isolated_dwelling = { pop = 5, minzoom = 10 }, 22 | farm = { pop = 5, minzoom = 10 }, 23 | island = { pop = 0, minzoom = 10 }, 24 | locality = { pop = 0, minzoom = 10 }, 25 | } 26 | 27 | -- --------------------------------------------------------------------------- 28 | 29 | local place_values = {} 30 | 31 | for key, _ in pairs(place_types) do 32 | table.insert(place_values, key) 33 | end 34 | 35 | themepark:add_table{ 36 | name = 'place_labels', 37 | ids_type = 'any', 38 | geom = 'point', 39 | columns = themepark:columns('core/name', { 40 | { column = 'kind', type = 'text', not_null = true }, 41 | { column = 'population', type = 'int', not_null = true }, 42 | { column = 'minzoom', type = 'int', not_null = true, tiles = 'minzoom' }, 43 | }), 44 | tags = { 45 | { key = 'capital', on = 'n' }, 46 | { key = 'place', on = 'n' }, 47 | { key = 'population', on = 'n' }, 48 | }, 49 | tiles = { 50 | minzoom = 4, 51 | order_by = 'population', 52 | order_dir = 'desc', 53 | }, 54 | expire = expire.shortbread(4, 14, 'place_labels', 'full-area') 55 | } 56 | 57 | -- --------------------------------------------------------------------------- 58 | 59 | local function place_columns(tags) 60 | if not tags.place then 61 | return nil 62 | end 63 | 64 | local place_type = place_types[tags.place] 65 | if not place_type then 66 | return nil 67 | end 68 | 69 | local attributes = { 70 | kind = tags.place, 71 | population = tonumber(tags.population) or place_type.pop, 72 | minzoom = place_type.minzoom 73 | } 74 | 75 | if tags.capital == 'yes' then 76 | if tags.place == 'city' or tags.place == 'town' or tags.place == 'village' or tags.place == 'hamlet' then 77 | attributes.kind = 'capital' 78 | attributes.minzoom = 4 79 | end 80 | elseif tags.capital == '4' then 81 | if tags.place == 'city' or tags.place == 'town' or tags.place == 'village' or tags.place == 'hamlet' then 82 | attributes.kind = 'state_capital' 83 | attributes.minzoom = 4 84 | end 85 | end 86 | return attributes 87 | end 88 | 89 | themepark:add_proc('node', function(object, data) 90 | local a = place_columns(object.tags) 91 | if not a then 92 | return 93 | end 94 | 95 | a.geom = object:as_point() 96 | 97 | themepark.themes.core.add_name(a, object) 98 | themepark:insert('place_labels', a, tags) 99 | end) 100 | 101 | themepark:add_proc('area', function(object, data) 102 | local a = place_columns(object.tags) 103 | if not a then 104 | return 105 | end 106 | 107 | a.geom = object:as_area():transform(3857):pole_of_inaccessibility() 108 | 109 | themepark.themes.core.add_name(a, object) 110 | themepark:insert('place_labels', a, tags) 111 | end) 112 | -- --------------------------------------------------------------------------- 113 | -------------------------------------------------------------------------------- /themes/shortbread/topics/pois.lua: -------------------------------------------------------------------------------- 1 | -- --------------------------------------------------------------------------- 2 | -- 3 | -- Theme: shortbread_v1 4 | -- Topic: pois 5 | -- 6 | -- --------------------------------------------------------------------------- 7 | 8 | local themepark, theme, cfg = ... 9 | local expire = require('expire') 10 | 11 | themepark:add_table{ 12 | name = 'pois', 13 | ids_type = 'any', 14 | geom = 'point', 15 | columns = themepark:columns('core/name', { 16 | { column = 'amenity', type = 'text' }, 17 | { column = 'leisure', type = 'text' }, 18 | { column = 'tourism', type = 'text' }, 19 | { column = 'shop', type = 'text' }, 20 | { column = 'man_made', type = 'text' }, 21 | { column = 'historic', type = 'text' }, 22 | { column = 'emergency', type = 'text' }, 23 | { column = 'highway', type = 'text' }, 24 | { column = 'office', type = 'text' }, 25 | { column = 'housename', type = 'text' }, 26 | { column = 'housenumber', type = 'text' }, 27 | { column = 'cuisine', type = 'text' }, 28 | { column = 'sport', type = 'text' }, 29 | { column = 'vending', type = 'text' }, 30 | { column = 'information', type = 'text' }, 31 | { column = 'tower:type', type = 'text' }, 32 | { column = 'religion', type = 'text' }, 33 | { column = 'denomination', type = 'text' }, 34 | { column = 'recycling:glass_bottles', type = 'bool' }, 35 | { column = 'recycling:paper', type = 'bool' }, 36 | { column = 'recycling:clothes', type = 'bool' }, 37 | { column = 'recycling:scrap_metal', type = 'bool' }, 38 | { column = 'atm', type = 'bool' }, 39 | }), 40 | tags = { 41 | }, 42 | tiles = { 43 | minzoom = 14, 44 | }, 45 | expire = expire.shortbread(14, 14, 'pois', 'full-area') 46 | 47 | } 48 | 49 | -- --------------------------------------------------------------------------- 50 | 51 | local get_value = {} 52 | 53 | get_value.amenity = osm2pgsql.make_check_values_func({ 54 | 'police', 'fire_station', 'post_box', 'post_office', 'telephone', 'library', 55 | 'townhall', 'courthouse', 'prison', 'embassy', 'community_centre', 56 | 'nursing_home', 'arts_centre', 'grave_yard', 'marketplace', 'recycling', 57 | 'university', 'school', 'college', 'public_building', 'pharmacy', 58 | 'hospital', 'clinic', 'doctors', 'dentist', 'veterinary', 'theatre', 59 | 'nightclub', 'cinema', 'restaurant', 'fast_food', 'cafe', 'pub', 'bar', 60 | 'food_court', 'biergarten', 'shelter', 'car_rental', 'car_wash', 61 | 'car_sharing', 'bicycle_rental', 'vending_machine', 'bank', 'atm', 62 | 'toilets', 'bench', 'drinking_water', 'fountain', 'hunting_stand', 63 | 'waste_basket', 'place_of_worship', 'playground', 'dog_park' 64 | }) 65 | 66 | get_value.leisure = osm2pgsql.make_check_values_func({ 67 | 'sports_centre', 'pitch', 'swimming_pool', 'water_park', 'golf_course', 68 | 'stadium', 'ice_rink', 69 | }) 70 | 71 | get_value.tourism = osm2pgsql.make_check_values_func({ 72 | 'hotel', 'motel', 'bed_and_breakfast', 'guest_house', 'hostel', 'chalet', 73 | 'camp_site', 'alpine_hut', 'caravan_site', 'information', 'picnic_site', 74 | 'viewpoint', 'zoo', 'theme_park', 75 | }) 76 | 77 | get_value.shop = osm2pgsql.make_check_values_func({ 78 | 'supermarket', 'bakery', 'kiosk', 'mall', 'department_store', 'general', 79 | 'convenience', 'clothes', 'florist', 'chemist', 'books', 'butcher', 80 | 'shoes', 'alcohol', 'beverages', 'optician', 'jewelry', 'gift', 'sports', 81 | 'stationery', 'outdoor', 'mobile_phone', 'toys', 'newsagent', 'greengrocer', 82 | 'beauty', 'video', 'car', 'bicycle', 'doityourself', 'hardware', 83 | 'furniture', 'computer', 'garden_centre', 'hairdresser', 'travel_agency', 84 | 'laundry', 'dry_cleaning', 85 | }) 86 | 87 | get_value.man_made = osm2pgsql.make_check_values_func({ 88 | 'surveillance', 'tower', 'windmill', 'lighthouse', 'wastewater_plant', 89 | 'water_well', 'watermill', 'water_works', 90 | }) 91 | 92 | get_value.historic = osm2pgsql.make_check_values_func({ 93 | 'monument', 'memorial', 'artwork', 'castle', 'ruins', 'archaelogical_site', 94 | 'wayside_cross', 'wayside_shrine', 'battlefield', 'fort', 95 | }) 96 | 97 | get_value.emergency = osm2pgsql.make_check_values_func({ 98 | 'phone', 'fire_hydrant', 'defibrillator' 99 | }) 100 | 101 | get_value.highway = osm2pgsql.make_check_values_func({ 102 | 'emergency_access_point' 103 | }) 104 | 105 | get_value.office = osm2pgsql.make_check_values_func({ 106 | 'diplomatic' 107 | }) 108 | 109 | -- --------------------------------------------------------------------------- 110 | 111 | local add_extra_attributes = {} 112 | 113 | add_extra_attributes.amenity = function(a, t) 114 | if t.amenity == 'vending_machine' then 115 | a.vending = t.vending 116 | elseif t.amenity == 'place_of_worship' then 117 | a.religion = t.religion 118 | a.denomination = t.denomination 119 | elseif t.amenity == 'restaurant' or t.amenity == 'fast_food' or 120 | t.amenity == 'pub' or t.amenity == 'bar' or t.amenity == 'cafe' then 121 | a.cuisine = t.cuisine 122 | elseif t.amenity == 'recycling' then 123 | a['recycling:glass_bottles'] = t['recycling:glass_bottles'] == 'yes' 124 | a['recycling:paper'] = t['recycling:paper'] == 'yes' 125 | a['recycling:clothes'] = t['recycling:clothes'] == 'yes' 126 | a['recycling:scrap_metal'] = t['recycling:scrap_metal'] == 'yes' 127 | elseif t.amenity == 'bank' then 128 | a.atm = t.atm == 'yes' 129 | end 130 | end 131 | 132 | add_extra_attributes.tourism = function(a, t) 133 | if t.tourism == 'information' then 134 | a.information = t.information 135 | end 136 | end 137 | 138 | add_extra_attributes.man_made = function(a, t) 139 | if t.man_made == 'tower' then 140 | a['tower:type'] = t['tower:type'] 141 | end 142 | end 143 | 144 | -- --------------------------------------------------------------------------- 145 | 146 | local get_attributes = function(object) 147 | local t = object.tags 148 | local a = {} 149 | 150 | local is_poi = false 151 | for _, k in ipairs({'amenity', 'leisure', 'tourism', 'shop', 'man_made', 152 | 'historic', 'emergency', 'highway', 'office'}) do 153 | local v = get_value[k](t[k]) 154 | if v then 155 | a[k] = v 156 | if add_extra_attributes[k] then 157 | add_extra_attributes[k](a, t) 158 | end 159 | is_poi = true 160 | end 161 | end 162 | 163 | if not is_poi then 164 | return nil 165 | end 166 | 167 | a.housename = t['addr:housename'] 168 | a.housenumber = t['addr:housenumber'] 169 | 170 | themepark.themes.core.add_name(a, object) 171 | themepark:add_debug_info(a, t) 172 | 173 | return a 174 | end 175 | 176 | -- --------------------------------------------------------------------------- 177 | 178 | themepark:add_proc('node', function(object, data) 179 | local a = get_attributes(object) 180 | if a then 181 | a.geom = object:as_point() 182 | themepark:insert('pois', a) 183 | data.shortbread_in_pois = true 184 | end 185 | end) 186 | 187 | themepark:add_proc('area', function(object, data) 188 | local a = get_attributes(object) 189 | if a then 190 | a.geom = object:as_area():centroid() 191 | themepark:insert('pois', a) 192 | data.shortbread_in_pois = true 193 | end 194 | end) 195 | 196 | -- --------------------------------------------------------------------------- 197 | -------------------------------------------------------------------------------- /themes/shortbread/topics/public_transport.lua: -------------------------------------------------------------------------------- 1 | -- --------------------------------------------------------------------------- 2 | -- 3 | -- Theme: shortbread_v1 4 | -- Topic: public_transport 5 | -- 6 | -- --------------------------------------------------------------------------- 7 | 8 | local themepark, theme, cfg = ... 9 | local expire = require('expire') 10 | 11 | themepark:add_table{ 12 | name = 'public_transport', 13 | ids_type = 'any', 14 | geom = 'point', 15 | columns = themepark:columns('core/name', { 16 | { column = 'kind', type = 'text', not_null = true }, 17 | { column = 'minzoom', type = 'int', tiles = 'minzoom' } 18 | }), 19 | tags = { 20 | { key = 'aerialway', value = 'station', on = 'na' }, 21 | { key = 'aeroway', values = { 'aerodrome', 'helipad' }, on = 'na' }, 22 | { key = 'amenity', values = { 'ferry_terminal', 'bus_station' }, on = 'na' }, 23 | { key = 'highway', value = 'bus_stop', on = 'na' }, 24 | { key = 'railway', values = { 'station', 'halt', 'tram_stop' }, on = 'na' }, 25 | }, 26 | tiles = { 27 | minzoom = 11, 28 | }, 29 | expire = expire.shortbread(11, 14, 'public_transport', 'full-area') 30 | } 31 | 32 | -- --------------------------------------------------------------------------- 33 | 34 | local get_attributes = function(object) 35 | local t = object.tags 36 | local a = {} 37 | 38 | if t.aeroway then 39 | if t.aeroway == 'aerodrome' then 40 | a.kind = 'aerodrome' 41 | a.minzoom = 11 42 | elseif t.aeroway == 'helipad' then 43 | a.kind = 'helipad' 44 | a.minzoom = 13 45 | else 46 | return nil 47 | end 48 | elseif t.railway then 49 | if t.railway == 'station' then 50 | a.kind = 'station' 51 | a.minzoom = 13 52 | elseif t.railway == 'halt' then 53 | a.kind = 'halt' 54 | a.minzoom = 13 55 | elseif t.railway == 'tram_stop' then 56 | a.kind = 'tram_stop' 57 | a.minzoom = 14 58 | else 59 | return nil 60 | end 61 | elseif t.amenity then 62 | if t.amenity == 'bus_station' then 63 | a.kind = 'bus_station' 64 | a.minzoom = 13 65 | elseif t.amenity == 'ferry_terminal' then 66 | a.kind = 'ferry_terminal' 67 | a.minzoom = 12 68 | else 69 | return nil 70 | end 71 | elseif t.highway and t.highway == 'bus_stop' then 72 | a.kind = 'bus_stop' 73 | a.minzoom = 14 74 | elseif t.aerialway and t.aerialway == 'station' then 75 | a.kind = 'aerialway_station' 76 | a.minzoom = 13 77 | else 78 | return nil 79 | end 80 | 81 | themepark.themes.core.add_name(a, object) 82 | 83 | return a 84 | end 85 | 86 | -- --------------------------------------------------------------------------- 87 | 88 | themepark:add_proc('node', function(object, data) 89 | local a = get_attributes(object) 90 | if a then 91 | a.geom = object:as_point() 92 | themepark:insert('public_transport', a, object.tags) 93 | end 94 | end) 95 | 96 | themepark:add_proc('area', function(object, data) 97 | local a = get_attributes(object) 98 | if a then 99 | a.geom = object:as_area():centroid() 100 | themepark:insert('public_transport', a, object.tags) 101 | end 102 | end) 103 | 104 | -- --------------------------------------------------------------------------- 105 | -------------------------------------------------------------------------------- /themes/shortbread/topics/sites.lua: -------------------------------------------------------------------------------- 1 | -- --------------------------------------------------------------------------- 2 | -- 3 | -- Theme: shortbread_v1 4 | -- Topic: sites 5 | -- 6 | -- --------------------------------------------------------------------------- 7 | 8 | local themepark, theme, cfg = ... 9 | local expire = require('expire') 10 | 11 | -- --------------------------------------------------------------------------- 12 | 13 | local amenity_values = { 'university', 'college', 'school', 'hospital', 14 | 'prison', 'parking', 'bicycle_parking' } 15 | 16 | -- --------------------------------------------------------------------------- 17 | 18 | themepark:add_table{ 19 | name = 'sites', 20 | ids_type = 'area', 21 | geom = 'multipolygon', 22 | columns = themepark:columns('core/name', { 23 | { column = 'kind', type = 'text', not_null = true }, 24 | }), 25 | tags = { 26 | { key = 'amenity', values = amenity_values, on = 'a' }, 27 | { key = 'landuse', value = 'construction', on = 'a' }, 28 | { key = 'leisure', value = 'sports_centre', on = 'a' }, 29 | { key = 'military', value = 'danger_area', on = 'a' }, 30 | }, 31 | tiles = { 32 | minzoom = 14, 33 | }, 34 | expire = expire.shortbread(14, 14, 'sites', 'full-area') 35 | } 36 | 37 | -- --------------------------------------------------------------------------- 38 | 39 | local get_amenity_value = osm2pgsql.make_check_values_func(amenity_values) 40 | 41 | -- --------------------------------------------------------------------------- 42 | 43 | themepark:add_proc('area', function(object, data) 44 | local t = object.tags 45 | local a = { 46 | kind = get_amenity_value(t.amenity) 47 | } 48 | 49 | if not a.kind then 50 | if t.military == 'danger_area' then 51 | a.kind = 'danger_area' 52 | elseif t.leisure == 'sports_centre' then 53 | a.kind = 'sports_centre' 54 | elseif t.landuse == 'construction' then 55 | a.kind = 'construction' 56 | else 57 | return 58 | end 59 | end 60 | 61 | a.geom = object:as_area() 62 | themepark.themes.core.add_name(a, object) 63 | themepark:insert('sites', a, t) 64 | end) 65 | 66 | -- --------------------------------------------------------------------------- 67 | -------------------------------------------------------------------------------- /themes/shortbread/topics/streets.lua: -------------------------------------------------------------------------------- 1 | -- --------------------------------------------------------------------------- 2 | -- 3 | -- Theme: shortbread_v1 4 | -- Topic: streets 5 | -- 6 | -- --------------------------------------------------------------------------- 7 | 8 | local themepark, theme, cfg = ... 9 | local expire = require('expire') 10 | 11 | themepark:add_table{ 12 | name = 'street_polygons', 13 | ids_type = 'way', 14 | geom = 'polygon', 15 | columns = themepark:columns({ 16 | { column = 'kind', type = 'text', not_null = true }, 17 | { column = 'rail', type = 'bool' }, 18 | { column = 'tunnel', type = 'bool' }, 19 | { column = 'bridge', type = 'bool' }, 20 | { column = 'surface', type = 'text' }, 21 | { column = 'z_order', type = 'int' }, 22 | }), 23 | tiles = { 24 | minzoom = 11, 25 | order_by = 'z_order', 26 | order_dir = 'desc', 27 | }, 28 | expire = expire.shortbread(11, 14, 'street_polygons', 'full-area') 29 | } 30 | 31 | themepark:add_table{ 32 | name = 'streets_polygons_labels', 33 | ids_type = 'area', 34 | geom = 'point', 35 | columns = themepark:columns('core/name', { 36 | { column = 'kind', type = 'text', not_null = true }, 37 | }), 38 | tiles = { 39 | minzoom = 14 40 | }, 41 | expire = expire.shortbread(14, 14, 'streets_polygons_labels', 'full-area') 42 | } 43 | 44 | themepark:add_table{ 45 | name = 'street_labels_points', 46 | ids_type = 'node', 47 | geom = 'point', 48 | columns = themepark:columns('core/name', { 49 | { column = 'kind', type = 'text' }, 50 | { column = 'ref', type = 'text' }, 51 | }), 52 | tiles = { 53 | minzoom = 12, 54 | }, 55 | expire = expire.shortbread(12, 14, 'street_labels_points', 'full-area') 56 | } 57 | 58 | -- --------------------------------------------------------------------------- 59 | 60 | local Z_STEP_PER_LAYER = 100 61 | 62 | local highway_lookup = { 63 | -- highway tag z minzoom 64 | motorway = { 34, 5 }, 65 | trunk = { 33, 6 }, 66 | primary = { 32, 8 }, 67 | secondary = { 31, 9 }, 68 | tertiary = { 30, 10 }, 69 | 70 | unclassified = { 20, 12 }, 71 | residential = { 20, 12 }, 72 | busway = { 20, 12 }, 73 | busway_guideway = { 20, 12 }, 74 | road = { 20, 12 }, 75 | 76 | tertiary_link = { 10, 12 }, 77 | secondary_link = { 10, 12 }, 78 | primary_link = { 10, 12 }, 79 | trunk_link = { 10, 12 }, 80 | motorway_link = { 10, 12 }, 81 | 82 | living_street = { 4, 13 }, 83 | pedestrian = { 4, 13 }, 84 | 85 | service = { 3, 13 }, 86 | track = { 3, 13 }, 87 | 88 | footway = { 2, 13 }, 89 | path = { 2, 13 }, 90 | cycleway = { 2, 13 }, 91 | bridleway = { 2, 13 }, 92 | 93 | steps = { 1, 13 }, 94 | platform = { 1, 13 }, 95 | } 96 | 97 | local railway_lookup = { 98 | rail = { 52, 8 }, 99 | narrow_gauge = { 51, 8 }, 100 | tram = { 51, 10 }, 101 | light_rail = { 51, 10 }, 102 | funicular = { 51, 10 }, 103 | subway = { 51, 10 }, 104 | monorail = { 51, 10 }, 105 | } 106 | 107 | local aeroway_lookup = { 108 | runway = 11, 109 | taxiway = 13, 110 | } 111 | 112 | local as_bool = function(value) 113 | return value == 'yes' or value == 'true' or value == '1' 114 | end 115 | 116 | local set_ref_attributes = function(a, t) 117 | if not t.ref then 118 | return 119 | end 120 | 121 | local refs = {} 122 | local rows = 0 123 | local cols = 0 124 | 125 | for word in string.gmatch(t.ref, "([^;]+);?") do 126 | word = word:gsub('^[%s]+', '', 1):gsub('[%s]+$', '', 1) 127 | rows = rows + 1 128 | cols = math.max(cols, string.len(word)) 129 | table.insert(refs, word) 130 | end 131 | 132 | a.ref = table.concat(refs, '\n') 133 | a.ref_rows = rows 134 | a.ref_cols = cols 135 | end 136 | 137 | -- --------------------------------------------------------------------------- 138 | 139 | themepark:add_proc('node', function(object, data) 140 | local t = object.tags 141 | 142 | if t.highway and t.highway == 'motorway_junction' then 143 | local a = { 144 | kind = t.highway, 145 | ref = t.ref, 146 | geom = object:as_point() 147 | } 148 | themepark.themes.core.add_name(a, object) 149 | themepark:insert('street_labels_points', a, t) 150 | end 151 | end) 152 | 153 | local process_as_area = function(object, data) 154 | if not object.is_closed then 155 | return 156 | end 157 | 158 | local t = object.tags 159 | local a = { 160 | layer = data.core.layer, 161 | } 162 | a.z_order = Z_STEP_PER_LAYER * a.layer 163 | 164 | if t.highway == 'pedestrian' or t.highway == 'service' then 165 | a.kind = t.highway 166 | elseif t.aeroway == 'runway' or t.aeroway == 'taxiway' then 167 | a.kind = t.aeroway 168 | else 169 | return 170 | end 171 | 172 | a.surface = t.surface 173 | 174 | a.tunnel = as_bool(t.tunnel) or t.tunnel == 'building_passage' or t.covered == 'yes' 175 | a.bridge = as_bool(t.bridge) 176 | 177 | a.geom = object:as_polygon():transform(3857) 178 | local has_name = themepark.themes.core.add_name(a, object) 179 | themepark:insert('street_polygons', a, t) 180 | 181 | if has_name then 182 | a.geom = a.geom:pole_of_inaccessibility() 183 | themepark:insert('streets_polygons_labels', a, t) 184 | end 185 | end 186 | 187 | themepark:add_proc('way', function(object, data) 188 | local t = object.tags 189 | if t.area == 'yes' then 190 | process_as_area(object, data) 191 | return 192 | end 193 | end) 194 | 195 | -- --------------------------------------------------------------------------- 196 | -------------------------------------------------------------------------------- /themes/shortbread/topics/water.lua: -------------------------------------------------------------------------------- 1 | -- --------------------------------------------------------------------------- 2 | -- 3 | -- Theme: shortbread_v1 4 | -- Topic: water 5 | -- 6 | -- --------------------------------------------------------------------------- 7 | 8 | local themepark, theme, cfg = ... 9 | local expire = require('expire') 10 | 11 | -- --------------------------------------------------------------------------- 12 | 13 | local waterway_values = { "river", "canal", "stream", "ditch" } 14 | 15 | local bridge_values = { 16 | 'yes', 'viaduct', 'boardwalk', 'cantilever', 'covered', 'low_water_crossing', 'movable', 'trestle' 17 | } 18 | 19 | local tunnel_values = { 'yes', 'building_passage' } 20 | 21 | -- --------------------------------------------------------------------------- 22 | 23 | themepark:add_table{ 24 | name = 'water_areas', 25 | ids_type = 'area', 26 | geom = 'multipolygon', 27 | columns = themepark:columns({ 28 | { column = 'kind', type = 'text', not_null = true }, 29 | { column = 'way_area', type = 'real' }, 30 | }), 31 | tags = { 32 | { key = 'landuse', values = { 'basin', 'reservoir' }, on = 'a' }, 33 | { key = 'natural', values = { 'water', 'glacier' }, on = 'a' }, 34 | { key = 'water', value = 'river', on = 'a' }, 35 | { key = 'waterway', values = { 'riverbank', 'dock', 'canal' }, on = 'a' }, 36 | }, 37 | tiles = { 38 | minzoom = 5 39 | }, 40 | expire = expire.shortbread(4, 14, 'water_polygons', 'full-area') 41 | } 42 | 43 | themepark:add_table{ 44 | name = 'water_area_labels', 45 | ids_type = 'area', 46 | geom = 'point', 47 | columns = themepark:columns('core/name', { 48 | { column = 'kind', type = 'text', not_null = true }, 49 | { column = 'way_area', type = 'real' }, 50 | }), 51 | tiles = { 52 | minzoom = 5, 53 | order_by = 'way_area', 54 | order_dir = 'desc', 55 | }, 56 | expire = expire.shortbread(5, 14, 'water_polygons_labels', 'full-area') 57 | } 58 | 59 | themepark:add_table{ 60 | name = 'water_lines', 61 | ids_type = 'way', 62 | geom = 'linestring', 63 | columns = themepark:columns({ 64 | { column = 'kind', type = 'text', not_null = true }, 65 | { column = 'tunnel', type = 'bool', not_null = true }, 66 | { column = 'bridge', type = 'bool', not_null = true }, 67 | { column = 'layer', type = 'int', not_null = true }, 68 | { column = 'minzoom', type = 'int', not_null = true, tiles = 'minzoom' }, 69 | }), 70 | tags = { 71 | { key = 'bridge', values = bridge_values, on = 'w' }, 72 | { key = 'covered', value = 'yes', on = 'w' }, 73 | { key = 'tunnel', values = tunnel_values, on = 'w' }, 74 | { key = 'waterway', values = waterway_values, on = 'w' }, 75 | }, 76 | tiles = { 77 | minzoom = 9, 78 | order_by = 'layer', 79 | order_dir = 'asc', 80 | }, 81 | expire = expire.shortbread(9, 14, 'water_lines', 'boundary-only') 82 | } 83 | 84 | themepark:add_table{ 85 | name = 'water_lines_labels', 86 | ids_type = 'way', 87 | geom = 'linestring', 88 | columns = themepark:columns('core/name', { 89 | { column = 'kind', type = 'text', not_null = true }, 90 | { column = 'minzoom', type = 'int', not_null = true }, 91 | }), 92 | tiles = { 93 | minzoom = 12, 94 | }, 95 | expire = expire.shortbread(12, 14, 'water_lines_labels', 'boundary-only') 96 | } 97 | 98 | -- --------------------------------------------------------------------------- 99 | 100 | local check_waterway = osm2pgsql.make_check_values_func(waterway_values) 101 | 102 | local round = function(value) 103 | return math.floor(value + 0.5) 104 | end 105 | 106 | local get_bridge_value = osm2pgsql.make_check_values_func(bridge_values, false) 107 | 108 | -- --------------------------------------------------------------------------- 109 | 110 | themepark:add_proc('way', function(object, data) 111 | local t = object.tags 112 | local waterway = t.waterway 113 | if check_waterway(waterway) then 114 | local a = { 115 | kind = waterway, 116 | geom = object:as_linestring(), 117 | layer = data.core.layer, 118 | bridge = get_bridge_value(t.bridge), 119 | tunnel = false, 120 | } 121 | 122 | if t.tunnel == 'yes' or t.tunnel == 'building_passage' or t.covered == 'yes' then 123 | a.tunnel = true 124 | end 125 | 126 | if a.kind == 'stream' or a.kind == 'ditch' then 127 | a.minzoom = 14 128 | else 129 | a.minzoom = 9 130 | end 131 | 132 | themepark:add_debug_info(a, t) 133 | themepark:insert('water_lines', a) 134 | 135 | if themepark.themes.core.add_name(a, object) then 136 | themepark:insert('water_lines_labels', a) 137 | end 138 | end 139 | end) 140 | 141 | themepark:add_proc('area', function(object, data) 142 | local t = object.tags 143 | local kind 144 | 145 | if t.natural == 'glacier' then 146 | kind = 'glacier' 147 | elseif t.natural == 'water' then 148 | if t.water == 'river' then 149 | kind = 'river' 150 | else 151 | kind = 'water' 152 | end 153 | elseif t.waterway == 'riverbank' then 154 | kind = 'river' 155 | elseif t.waterway == 'dock' or t.waterway == 'canal' then 156 | kind = t.waterway 157 | elseif t.landuse == 'basin' or t.landuse == 'reservoir' then 158 | kind = t.landuse 159 | end 160 | 161 | if not kind then 162 | return 163 | end 164 | 165 | local g = object:as_area():transform(3857) 166 | local a = { 167 | kind = kind, 168 | way_area = round(g:area()), 169 | geom = g 170 | } 171 | themepark:insert('water_areas', a) 172 | 173 | if themepark.themes.core.add_name(a, object) then 174 | a.geom = g:pole_of_inaccessibility() 175 | themepark:insert('water_area_labels', a) 176 | end 177 | end) 178 | 179 | -- --------------------------------------------------------------------------- 180 | -------------------------------------------------------------------------------- /themes/spirit/common.lua: -------------------------------------------------------------------------------- 1 | local function contains(list, x) 2 | for i = 1, #list do 3 | if list[i] == x then return true end 4 | end 5 | return false 6 | end 7 | 8 | --- Normalizes layer tags to integers 9 | -- @param v The layer tag value 10 | -- @return The input value if it is an integer between -100 and 100, or nil otherwise 11 | local function layer (v) 12 | if v and string.find(v, "^-?%d+$") and tonumber(v) < 100 and tonumber(v) > -100 then -- check if value exists, is numeric, and is in range 13 | return v 14 | end 15 | return nil 16 | end 17 | 18 | return { contains=contains, layer=layer} 19 | -------------------------------------------------------------------------------- /themes/spirit/init.lua: -------------------------------------------------------------------------------- 1 | -- --------------------------------------------------------------------------- 2 | -- 3 | -- Theme: spirit 4 | -- 5 | -- --------------------------------------------------------------------------- 6 | 7 | local theme = {} 8 | 9 | return theme 10 | 11 | -- --------------------------------------------------------------------------- 12 | -------------------------------------------------------------------------------- /themes/spirit/topics/admin.lua: -------------------------------------------------------------------------------- 1 | -- --------------------------------------------------------------------------- 2 | -- 3 | -- Theme: spirit 4 | -- Topic: admin 5 | -- 6 | -- --------------------------------------------------------------------------- 7 | 8 | local themepark, theme, cfg = ... 9 | 10 | --- Normalizes admin_level tags 11 | -- @param v The admin_level tag value 12 | -- @return The input value if it is an integer between 0 and 100, or nil otherwise 13 | local function admin_level (v) 14 | if v and string.find(v, "^%d+$") and tonumber(v) < 100 and tonumber(v) > 0 then 15 | return tonumber(v) 16 | end 17 | return nil 18 | end 19 | 20 | local phase2_admin_ways_level = {} 21 | local phase2_admin_ways_parents = {} 22 | 23 | themepark:add_table{ 24 | name = 'admin', 25 | ids_type = 'relation', 26 | geom = 'point', -- the primary geom used is the label, but areas are needed for admin line processing 27 | columns = themepark:columns({ 28 | { column = 'name', type = 'text' }, 29 | { column = 'admin_level', type = 'smallint'}, 30 | { column = 'way_area', type = 'real' }, 31 | { column = 'area', type = 'geometry'} 32 | }), 33 | } 34 | 35 | themepark:add_table{ 36 | name = 'admin_lines', 37 | ids_type = 'way', 38 | geom = 'linestring', 39 | columns = themepark:columns({ 40 | { column = 'min_admin_level', type = 'smallint' }, 41 | { column = 'multiple_relations', type = 'boolean'} 42 | }) 43 | } 44 | 45 | themepark:add_proc('area', function(object, data) 46 | if object.type == 'relation' and object.tags.type == 'boundary' 47 | and object.tags.boundary == 'administrative' then 48 | local admin = admin_level(object.tags.admin_level) 49 | if admin and admin >= 2 and admin <= 12 then 50 | g = object:as_area():transform(3857) 51 | local a = { 52 | geom = g:pole_of_inaccessibility(), 53 | area = g, 54 | way_area = g:area(), 55 | admin_level = admin, 56 | name = object.tags.name 57 | } 58 | themepark:add_debug_info(a, object.tags) 59 | themepark:insert('admin', a) 60 | end 61 | end 62 | end) 63 | 64 | themepark:add_proc('relation', function(object, data) 65 | if object.tags.type == 'boundary' and object.tags.boundary == 'administrative' then 66 | local admin = admin_level(object.tags.admin_level) 67 | if admin ~= nil then 68 | for _, member in ipairs(object.members) do 69 | if member.type == 'w' then 70 | -- Store the lowest admin_level, and how many relations it used in 71 | if not phase2_admin_ways_level[member.ref] then 72 | phase2_admin_ways_level[member.ref] = admin 73 | phase2_admin_ways_parents[member.ref] = 1 74 | else 75 | if phase2_admin_ways_level[member.ref] == admin then 76 | phase2_admin_ways_parents[member.ref] = phase2_admin_ways_parents[member.ref] + 1 77 | elseif admin < phase2_admin_ways_level[member.ref] then 78 | phase2_admin_ways_level[member.ref] = admin 79 | phase2_admin_ways_parents[member.ref] = 1 80 | end 81 | end 82 | end 83 | end 84 | end 85 | end 86 | end) 87 | 88 | themepark:add_proc('select_relation_members', function(relation) 89 | if relation.tags.type == 'boundary' and relation.tags.boundary == 'administrative' 90 | and admin_level(relation.tags.admin_level) ~= nil then 91 | return { ways = osm2pgsql.way_member_ids(relation) } 92 | end 93 | end) 94 | 95 | themepark:add_proc('way', function(object, data) 96 | if osm2pgsql.stage == 1 then 97 | return 98 | end 99 | 100 | if phase2_admin_ways_level[object.id] and object.tags.closure_segment ~= 'yes' then 101 | local a = { 102 | geom = object:as_linestring(), 103 | min_admin_level = phase2_admin_ways_level[object.id], 104 | multiple_relations = (phase2_admin_ways_parents[object.id] > 1) 105 | } 106 | themepark:insert('admin_lines', a) 107 | end 108 | end) 109 | -------------------------------------------------------------------------------- /themes/spirit/topics/aeroway.lua: -------------------------------------------------------------------------------- 1 | -- --------------------------------------------------------------------------- 2 | -- 3 | -- Theme: spirit 4 | -- Topic: aeroway 5 | -- 6 | -- Airports are already present in the transit layer, so this is just more airport-focused stuff 7 | -- 8 | -- --------------------------------------------------------------------------- 9 | 10 | 11 | local themepark, theme, cfg = ... 12 | local common = require('themes.spirit.common') 13 | local expire = require('expire') 14 | 15 | themepark:add_table{ 16 | name = 'aeroways', 17 | ids_type = 'way', 18 | geom = 'linestring', 19 | columns = themepark:columns({ 20 | { column = 'ref', type = 'text' }, 21 | { column = 'aeroway', type = 'text' }, 22 | }), 23 | expire = expire.shortbread(11, 14, 'streets', 'boundary-only') 24 | } 25 | 26 | themepark:add_proc('way', function(object, data) 27 | if object.tags.aeroway == 'runway' 28 | or object.tags.aeroway == 'taxiway' then 29 | local a = { aeroway = object.tags.aeroway, 30 | ref = object.tags.ref, 31 | geom = object:as_linestring() } 32 | themepark:add_debug_info(a, object.tags) 33 | themepark:insert('aeroways', a) 34 | end 35 | end) 36 | -------------------------------------------------------------------------------- /themes/spirit/topics/buildings.lua: -------------------------------------------------------------------------------- 1 | -- --------------------------------------------------------------------------- 2 | -- 3 | -- Theme: spirit 4 | -- Topic: buildings 5 | -- 6 | -- --------------------------------------------------------------------------- 7 | 8 | local themepark, theme, cfg = ... 9 | local common = require('themes.spirit.common') 10 | local expire = require('expire') 11 | 12 | themepark:add_table{ 13 | name = 'buildings', 14 | ids_type = 'area', 15 | geom = 'polygon', 16 | columns = themepark:columns({ 17 | { column = 'name', type = 'text' }, 18 | { column = 'way_area', type = 'real' }, 19 | { column = 'point', type = 'point' }, 20 | }), 21 | indexes = { 22 | { method = 'gist', column = 'point' }, 23 | }, 24 | expire = expire.shortbread(14, 14, 'buildings', 'full-area') 25 | } 26 | 27 | themepark:add_proc('area', function(object, data) 28 | if object.tags.building and object.tags.building ~= 'no' then 29 | for g in object:as_area():geometries() do 30 | local g_transform = g:transform(3857) 31 | local name = object.tags.name 32 | local a = { name = name, way_area = g_transform:area(), geom = g_transform } 33 | -- Only add points for buildings that need labels 34 | if name then 35 | a.point = g_transform:pole_of_inaccessibility() 36 | end 37 | themepark:add_debug_info(a, object.tags) 38 | themepark:insert('buildings', a) 39 | end 40 | end 41 | end) 42 | 43 | -------------------------------------------------------------------------------- /themes/spirit/topics/education.lua: -------------------------------------------------------------------------------- 1 | -- --------------------------------------------------------------------------- 2 | -- 3 | -- Theme: spirit 4 | -- Topic: education 5 | -- 6 | -- --------------------------------------------------------------------------- 7 | 8 | local common = require ('themes.spirit.common') 9 | 10 | local themepark, theme, cfg = ... 11 | 12 | themepark:add_table{ 13 | name = 'education', 14 | ids_type = 'any', 15 | geom = 'multipolygon', 16 | columns = themepark:columns({ 17 | { column = 'name', type = 'text' }, 18 | { column = 'education', type = 'text' }, 19 | { column = 'way_area', type = 'real' }, 20 | { column = 'point', type = 'point' }, 21 | }), 22 | indexes = { 23 | { method = 'gist', column = 'point' }, 24 | } 25 | } 26 | 27 | themepark:add_proc('node', function(object, data) 28 | local education 29 | if object.tags.amenity == 'school' then 30 | education = 'school' 31 | elseif object.tags.amenity == 'kindergarten' then 32 | education = 'kindergarten' 33 | elseif object.tags.amenity == 'university' then 34 | education = 'university' 35 | elseif object.tags.amenity == 'college' then 36 | education = 'college' 37 | end 38 | if education ~= nil then 39 | local a = { 40 | point = object:as_point(), 41 | name = object.tags.name, 42 | education = education } 43 | themepark:add_debug_info(a, object.tags) 44 | themepark:insert('education', a) 45 | end 46 | end) 47 | 48 | themepark:add_proc('area', function(object, data) 49 | local education 50 | if object.tags.amenity == 'school' then 51 | education = 'school' 52 | elseif object.tags.amenity == 'kindergarten' then 53 | education = 'kindergarten' 54 | elseif object.tags.amenity == 'university' then 55 | education = 'university' 56 | elseif object.tags.amenity == 'college' then 57 | education = 'college' 58 | end 59 | if education ~= nil then 60 | local g_transform = object:as_area():transform(3857) 61 | local a = { 62 | geom = g_transform, 63 | point = g_transform:pole_of_inaccessibility(), 64 | way_area = g_transform:area(), 65 | name = object.tags.name, 66 | education = education } 67 | themepark:add_debug_info(a, object.tags) 68 | themepark:insert('education', a) 69 | end 70 | end) 71 | -------------------------------------------------------------------------------- /themes/spirit/topics/food.lua: -------------------------------------------------------------------------------- 1 | -- --------------------------------------------------------------------------- 2 | -- 3 | -- Theme: spirit 4 | -- Topic: food 5 | -- 6 | -- --------------------------------------------------------------------------- 7 | 8 | local common = require ('themes.spirit.common') 9 | 10 | local themepark, theme, cfg = ... 11 | 12 | themepark:add_table{ 13 | name = 'food', 14 | ids_type = 'any', 15 | geom = 'multipolygon', 16 | columns = themepark:columns({ 17 | { column = 'name', type = 'text' }, 18 | { column = 'food', type = 'text' }, 19 | { column = 'way_area', type = 'real' }, 20 | { column = 'point', type = 'point' }, 21 | }), 22 | indexes = { 23 | { method = 'gist', column = 'point' }, 24 | } 25 | } 26 | 27 | local amenities = { 'bar', 'biergarten', 'cafe', 'fast_food', 'food_court', 'ice_cream', 'pub', 'restaurant' } 28 | 29 | themepark:add_proc('node', function(object, data) 30 | if object.tags.amenity and common.contains(amenities, object.tags.amenity) then 31 | local a = { 32 | point = object:as_point(), 33 | name = object.tags.name, 34 | food = object.tags.amenity } 35 | themepark:add_debug_info(a, object.tags) 36 | themepark:insert('food', a) 37 | end 38 | end) 39 | 40 | themepark:add_proc('area', function(object, data) 41 | if object.tags.amenity and common.contains(amenities, object.tags.amenity) then 42 | local g_transform = object:as_area():transform(3857) 43 | local a = { 44 | geom = g_transform, 45 | point = g_transform:pole_of_inaccessibility(), 46 | way_area = g_transform:area(), 47 | name = object.tags.name, 48 | food = object.tags.amenity } 49 | themepark:add_debug_info(a, object.tags) 50 | themepark:insert('food', a) 51 | end 52 | end) 53 | -------------------------------------------------------------------------------- /themes/spirit/topics/landuse.lua: -------------------------------------------------------------------------------- 1 | -- --------------------------------------------------------------------------- 2 | -- 3 | -- Theme: spirit 4 | -- Topic: landuse 5 | -- 6 | -- --------------------------------------------------------------------------- 7 | 8 | local common = require ('themes.spirit.common') 9 | 10 | local themepark, theme, cfg = ... 11 | 12 | themepark:add_table{ 13 | name = 'landuse', 14 | ids_type = 'area', 15 | geom = 'multipolygon', 16 | columns = themepark:columns({ 17 | { column = 'name', type = 'text' }, 18 | { column = 'landuse', type = 'text' }, 19 | { column = 'way_area', type = 'real' }, 20 | { column = 'point', type = 'point' }, 21 | }), 22 | indexes = { 23 | { method = 'gist', column = 'point' }, 24 | } 25 | } 26 | 27 | themepark:add_proc('area', function(object, data) 28 | local landuse 29 | if object.tags.landuse == 'residential' then 30 | landuse = 'residential' 31 | elseif object.tags.natural == 'commercial' then 32 | landuse = 'commercial' 33 | elseif object.tags.natural == 'retail' then 34 | landuse = 'retail' 35 | elseif object.tags.natural == 'industrial' then 36 | landuse = 'industrial' 37 | end 38 | 39 | if landuse ~= nil then 40 | local g_transform = object:as_area():transform(3857) 41 | local a = { 42 | name = object.tags.name, 43 | landuse = landuse, 44 | way_area = g_transform:area(), 45 | geom = g_transform } 46 | 47 | if object.tags.name then 48 | a.point = g_transform:pole_of_inaccessibility() 49 | end 50 | themepark:add_debug_info(a, object.tags) 51 | themepark:insert('landuse', a) 52 | end 53 | end) 54 | -------------------------------------------------------------------------------- /themes/spirit/topics/leisure.lua: -------------------------------------------------------------------------------- 1 | -- --------------------------------------------------------------------------- 2 | -- 3 | -- Theme: spirit 4 | -- Topic: leisure 5 | -- 6 | -- --------------------------------------------------------------------------- 7 | 8 | local common = require ('themes.spirit.common') 9 | 10 | local themepark, theme, cfg = ... 11 | 12 | themepark:add_table{ 13 | name = 'leisure', 14 | ids_type = 'area', 15 | geom = 'multipolygon', 16 | columns = themepark:columns({ 17 | { column = 'name', type = 'text' }, 18 | { column = 'leisure', type = 'text' }, 19 | { column = 'way_area', type = 'real' }, 20 | { column = 'point', type = 'point' }, 21 | }), 22 | indexes = { 23 | { method = 'gist', column = 'point' }, 24 | } 25 | } 26 | 27 | themepark:add_proc('area', function(object, data) 28 | local leisure 29 | if object.tags.leisure == 'park' then 30 | leisure = 'park' 31 | elseif object.tags.natural == 'stadium' then 32 | leisure = 'stadium' 33 | elseif object.tags.natural == 'playground' then 34 | leisure = 'playground' 35 | end 36 | 37 | if leisure ~= nil then 38 | local g_transform = object:as_area():transform(3857) 39 | local a = { 40 | name = object.tags.name, 41 | leisure = leisure, 42 | way_area = g_transform:area(), 43 | geom = g_transform } 44 | 45 | if object.tags.name then 46 | a.point = g_transform:pole_of_inaccessibility() 47 | end 48 | themepark:add_debug_info(a, object.tags) 49 | themepark:insert('leisure', a) 50 | end 51 | end) 52 | -------------------------------------------------------------------------------- /themes/spirit/topics/places.lua: -------------------------------------------------------------------------------- 1 | -- --------------------------------------------------------------------------- 2 | -- 3 | -- Theme: spirit 4 | -- Topic: places 5 | -- 6 | -- --------------------------------------------------------------------------- 7 | 8 | local common = require ('themes.spirit.common') 9 | 10 | local themepark, theme, cfg = ... 11 | 12 | themepark:add_table{ 13 | name = 'settlements', 14 | ids_type = 'any', 15 | way_area, 'real', 16 | geom = 'point', 17 | columns = themepark:columns({ 18 | { column = 'name', type = 'text' }, 19 | { column = 'place', type = 'text' }, 20 | { column = 'way_area', type = 'real' }, 21 | }), 22 | } 23 | 24 | themepark:add_proc('node', function(object, data) 25 | local place 26 | if object.tags.place == 'city' then 27 | place = 'city' 28 | elseif object.tags.place == 'town' then 29 | place = 'town' 30 | elseif object.tags.place == 'village' then 31 | place = 'village' 32 | elseif object.tags.place == 'hamlet' then 33 | place = 'hamlet' 34 | elseif object.tags.place == 'isolated_dwelling' then 35 | place = 'isolated_dwelling' 36 | end 37 | 38 | if place ~= nil then 39 | local a = { 40 | geom = object:as_point(), 41 | place = place, 42 | name = object.tags.name } 43 | themepark:add_debug_info(a, object.tags) 44 | themepark:insert('settlements', a) 45 | end 46 | end) 47 | 48 | themepark:add_proc('area', function(object, data) 49 | local place 50 | if object.tags.place == 'city' then 51 | place = 'city' 52 | elseif object.tags.place == 'town' then 53 | place = 'town' 54 | elseif object.tags.place == 'village' then 55 | place = 'village' 56 | elseif object.tags.place == 'hamlet' then 57 | place = 'hamlet' 58 | elseif object.tags.place == 'isolated_dwelling' then 59 | place = 'isolated_dwelling' 60 | end 61 | 62 | if place ~= nil then 63 | local g = object:as_area():transform(3857) 64 | local a = { 65 | geom = g:pole_of_inaccessibility(), 66 | way_area = g:area(), 67 | place = place, 68 | name = object.tags.name } 69 | themepark:add_debug_info(a, object.tags) 70 | themepark:insert('settlements', a) 71 | end 72 | end) 73 | -------------------------------------------------------------------------------- /themes/spirit/topics/railway.lua: -------------------------------------------------------------------------------- 1 | -- --------------------------------------------------------------------------- 2 | -- 3 | -- Theme: spirit 4 | -- Topic: railway 5 | -- 6 | -- --------------------------------------------------------------------------- 7 | 8 | local themepark, theme, cfg = ... 9 | local common = require('themes.spirit.common') 10 | local expire = require('expire') 11 | 12 | themepark:add_table{ 13 | name = 'railways', 14 | ids_type = 'way', 15 | geom = 'linestring', 16 | columns = themepark:columns({ 17 | { column = 'railway', type = 'text' }, 18 | { column = 'name', type = 'text' }, 19 | { column = 'ref', type = 'text' }, 20 | { column = 'minor', type = 'boolean' }, 21 | { column = 'bridge', type = 'boolean' }, 22 | { column = 'tunnel', type = 'boolean' }, 23 | { column = 'layer', type = 'smallint' }, 24 | { column = 'z_order', type = 'smallint' }, 25 | { column = 'service', type = 'text' }, 26 | }), 27 | expire = expire.shortbread(8, 14, 'streets', 'boundary-only') 28 | } 29 | 30 | local z_order = { 31 | rail = 440, 32 | narrow_gauge = 430, 33 | light_rail = 420, 34 | funicular = 420, 35 | subway = 420, 36 | monorail = 420, 37 | tram = 410 38 | } 39 | 40 | local ssy = {'spur', 'siding', 'yard'} 41 | themepark:add_proc('way', function(object, data) 42 | local z = z_order[object.tags.railway] 43 | if z then 44 | local a = { name = object.tags.name, 45 | ref = object.tags.ref, 46 | railway = object.tags.railway, 47 | service = object.tags.service, 48 | layer = common.layer(object.tags.layer), 49 | z_order = z, 50 | geom = object:as_linestring() } 51 | if common.contains(ssy, object.tags.service) then 52 | a.minor = true 53 | end 54 | if object.tags.bridge and object.tags.bridge ~= 'no' then 55 | a.bridge = true 56 | end 57 | if object.tags.tunnel and object.tags.tunnel ~= 'no' then 58 | a.tunnel = true 59 | end 60 | 61 | themepark:add_debug_info(a, object.tags) 62 | themepark:insert('railways', a) 63 | end 64 | end) 65 | 66 | 67 | -------------------------------------------------------------------------------- /themes/spirit/topics/roads.lua: -------------------------------------------------------------------------------- 1 | -- --------------------------------------------------------------------------- 2 | -- 3 | -- Theme: spirit 4 | -- Topic: roads 5 | -- 6 | -- --------------------------------------------------------------------------- 7 | 8 | local themepark, theme, cfg = ... 9 | local common = require('themes.spirit.common') 10 | local expire = require('expire') 11 | 12 | themepark:add_table{ 13 | name = 'roads', 14 | ids_type = 'way', 15 | geom = 'linestring', 16 | columns = themepark:columns({ 17 | { column = 'highway', type = 'text' }, 18 | { column = 'name', type = 'text' }, 19 | { column = 'ref', type = 'text' }, 20 | { column = 'oneway', type = 'text' }, 21 | { column = 'minor', type = 'boolean' }, 22 | { column = 'bridge', type = 'boolean' }, 23 | { column = 'tunnel', type = 'boolean' }, 24 | { column = 'layer', type = 'smallint' }, 25 | { column = 'z_order', type = 'smallint' }, 26 | { column = 'tracktype', type = 'text' }, 27 | { column = 'surface', type = 'text' }, 28 | { column = 'service', type = 'text' }, 29 | { column = 'bicycle', type = 'text' }, 30 | { column = 'horse', type = 'text' } 31 | }), 32 | indexes = { 33 | { 34 | column = 'geom', 35 | method = 'gist', 36 | where = "highway IN ('motorway', 'motorway_link', 'trunk', 'trunk_link', 'primary', 'primary_link', 'secondary', 'secondary_link', 'tertiary', 'tertiary_link')" 37 | } 38 | }, 39 | expire = expire.shortbread(5, 14, 'streets', 'boundary-only') 40 | } 41 | 42 | themepark:add_table{ 43 | name = 'road_routes', 44 | ids_type = 'relation', 45 | columns = themepark:columns({ 46 | { column = 'member_id', type = 'int8' }, 47 | { column = 'member_position', type = 'int4' }, 48 | { column = 'ref', type = 'text' }, 49 | { column = 'network', type = 'text' } 50 | }), 51 | } 52 | 53 | -- z_order value. Must be a multiple of 10, because construction divides it by 10 54 | local z_order = { 55 | motorway = 380, 56 | trunk = 370, 57 | primary = 360, 58 | secondary = 350, 59 | tertiary = 340, 60 | road = 330, 61 | unclassified = 330, 62 | residential = 330, 63 | living_street = 320, 64 | pedestrian = 310, 65 | motorway_link = 240, 66 | trunk_link = 230, 67 | primary_link = 220, 68 | secondary_link = 210, 69 | tertiary_link = 200, 70 | busway = 170, 71 | bus_guideway = 170, 72 | service = 150, 73 | track = 110, 74 | bridleway = 100, 75 | footway = 100, 76 | cycleway = 100, 77 | path = 100, 78 | steps = 100, 79 | construction = 0 80 | } 81 | 82 | local minor_service = {'parking_aisle', 'drive-through', 'driveway'} 83 | themepark:add_proc('way', function(object, data) 84 | local z = z_order[object.tags.highway] 85 | if z and (not object.tags.area or object.tags.area == 'no') then 86 | if object.tags.highway == 'construction' then 87 | if object.tags.construction and z_order[object.tags.construction] then 88 | z = z_order[object.tags.construction]/10 89 | else 90 | z = z_order['road']/10 91 | end 92 | end 93 | local a = { name = object.tags.name, 94 | highway = object.tags.highway, 95 | ref = object.tags.ref, 96 | oneway = object.tags.oneway, 97 | tracktype = object.tags.oneway, 98 | surface = object.tags.surface, 99 | service = object.tags.service, 100 | bicycle = object.tags.bicycle, 101 | horse = object.tags.horse, 102 | layer = common.layer(object.tags.layer), 103 | z_order = z, 104 | geom = object:as_linestring() } 105 | if common.contains(minor_service, object.tags.service) then 106 | a.minor = true 107 | end 108 | if object.tags.bridge and object.tags.bridge ~= 'no' then 109 | a.bridge = true 110 | end 111 | if object.tags.tunnel and object.tags.tunnel ~= 'no' then 112 | a.tunnel = true 113 | end 114 | 115 | themepark:add_debug_info(a, object.tags) 116 | themepark:insert('roads', a) 117 | end 118 | end) 119 | 120 | themepark:add_proc('relation', function(object) 121 | if object.tags.type == 'route' and object.tags.route == 'road' then 122 | local a = { ref = object.tags.ref, network = object.tags.network } 123 | for i, member in ipairs(object.members) do 124 | if member.type == 'w' then 125 | a.member_id = member.ref 126 | a.member_position = id 127 | themepark:insert('road_routes', a) 128 | end 129 | end 130 | end 131 | end) 132 | -------------------------------------------------------------------------------- /themes/spirit/topics/transit.lua: -------------------------------------------------------------------------------- 1 | -- --------------------------------------------------------------------------- 2 | -- 3 | -- Theme: spirit 4 | -- Topic: transit 5 | -- 6 | -- --------------------------------------------------------------------------- 7 | 8 | local common = require ('themes.spirit.common') 9 | 10 | local themepark, theme, cfg = ... 11 | 12 | themepark:add_table{ 13 | name = 'transit', 14 | ids_type = 'any', 15 | way_area, 'real', 16 | geom = 'point', 17 | columns = themepark:columns({ 18 | { column = 'name', type = 'text' }, 19 | { column = 'station', type = 'boolean' }, 20 | { column = 'mode', type = 'text' }, 21 | { column = 'way_area', type = 'real' }, 22 | }), 23 | } 24 | 25 | themepark:add_proc('node', function(object, data) 26 | local mode 27 | local station 28 | if object.tags.aeroway == 'aerodrome' then 29 | mode = 'airplane' 30 | station = true 31 | elseif (object.tags.railway == 'station' and object.tags.station == 'subway') 32 | or (object.tags.public_transport == 'station' and object.tags.subway == 'yes') then 33 | mode = 'subway' 34 | station = true 35 | elseif object.tags.highway == 'tram_stop' or (object.tags.public_transport == 'platform' and object.tags.tram == 'yes') then 36 | mode = 'tram' 37 | station = false 38 | elseif object.tags.highway == 'bus_stop' 39 | or (object.tags.public_transport == 'platform' and object.tags.bus == 'yes') then 40 | mode = 'bus' 41 | station = false 42 | elseif object.tags.highway == 'bus_station' 43 | or (object.tags.public_transport == 'station' and object.tags.bus == 'yes') then 44 | mode = 'bus' 45 | station = true 46 | end 47 | 48 | if mode ~= nil then 49 | local a = { 50 | geom = object:as_point(), 51 | mode = mode, 52 | station = station, 53 | name = object.tags.name } 54 | themepark:add_debug_info(a, object.tags) 55 | themepark:insert('transit', a) 56 | end 57 | end) 58 | 59 | themepark:add_proc('area', function(object, data) 60 | local mode 61 | local station 62 | -- The logic here is similar to node handling, but differs as some tagging is node-only 63 | if object.tags.aeroway == 'aerodrome' then 64 | mode = 'airplane' 65 | station = true 66 | elseif (object.tags.railway == 'station' and object.tags.station == 'subway') 67 | or (object.tags.public_transport == 'station' and object.tags.subway == 'yes') then 68 | mode = 'subway' 69 | station = true 70 | elseif object.tags.highway == 'tram_stop' or (object.tags.public_transport == 'platform' and object.tags.tram == 'yes') then 71 | mode = 'tram' 72 | station = false 73 | elseif object.tags.highway == 'bus_station' 74 | or (object.tags.public_transport == 'station' and object.tags.bus == 'yes') then 75 | mode = 'bus' 76 | station = true 77 | end 78 | 79 | if mode ~= nil then 80 | local g = object:as_area():transform(3857) 81 | local a = { 82 | geom = g:pole_of_inaccessibility(), 83 | way_area = g:area(), 84 | mode = mode, 85 | station = station, 86 | name = object.tags.name } 87 | themepark:add_debug_info(a, object.tags) 88 | themepark:insert('transit', a) 89 | end 90 | end) 91 | -------------------------------------------------------------------------------- /themes/spirit/topics/vegetation.lua: -------------------------------------------------------------------------------- 1 | -- --------------------------------------------------------------------------- 2 | -- 3 | -- Theme: spirit 4 | -- Topic: vegetation 5 | -- 6 | -- --------------------------------------------------------------------------- 7 | 8 | local common = require ('themes.spirit.common') 9 | 10 | local themepark, theme, cfg = ... 11 | 12 | themepark:add_table{ 13 | name = 'vegetation', 14 | ids_type = 'area', 15 | geom = 'multipolygon', 16 | columns = themepark:columns({ 17 | { column = 'name', type = 'text' }, 18 | { column = 'vegetation', type = 'text' }, 19 | { column = 'wetland', type = 'text' }, 20 | { column = 'way_area', type = 'real' }, 21 | { column = 'point', type = 'point' }, 22 | }), 23 | indexes = { 24 | { method = 'gist', column = 'point' }, 25 | } 26 | } 27 | 28 | themepark:add_proc('area', function(object, data) 29 | local vegetation 30 | local wetland 31 | if object.tags.natural == 'wood' or object.tags.landuse == 'forest' then 32 | vegetation = 'wood' 33 | elseif object.tags.natural == 'heath' then 34 | vegetation = 'heath' 35 | elseif object.tags.natural == 'scrub' then 36 | vegetation = 'scrub' 37 | elseif object.tags.natural == 'grassland' or object.tags.landuse == 'meadow' or object.tags.landuse == 'grass' then 38 | vegetation = 'grass' 39 | elseif object.tags.natural == 'mud' then 40 | vegetation = 'wetland' 41 | wetland = 'mud' 42 | elseif object.tags.natural == 'wetland' then 43 | vegetation = 'wetland' 44 | wetland = object.tags.wetland 45 | end 46 | 47 | if vegetation ~= nil then 48 | local g_transform = object:as_area():transform(3857) 49 | local a = { 50 | name = object.tags.name, 51 | vegetation = vegetation, 52 | wetland = wetland, 53 | way_area = g_transform:area(), 54 | geom = g_transform } 55 | 56 | if object.tags.name then 57 | a.point = g_transform:pole_of_inaccessibility() 58 | end 59 | themepark:add_debug_info(a, object.tags) 60 | themepark:insert('vegetation', a) 61 | end 62 | end) 63 | -------------------------------------------------------------------------------- /themes/spirit/topics/water.lua: -------------------------------------------------------------------------------- 1 | -- --------------------------------------------------------------------------- 2 | -- 3 | -- Theme: spirit 4 | -- Topic: water 5 | -- 6 | -- --------------------------------------------------------------------------- 7 | 8 | local common = require ('themes.spirit.common') 9 | 10 | local themepark, theme, cfg = ... 11 | 12 | themepark:add_table{ 13 | name = 'water', 14 | ids_type = 'area', 15 | geom = 'multipolygon', 16 | columns = themepark:columns({ 17 | { column = 'name', type = 'text' }, 18 | { column = 'way_area', type = 'real' }, 19 | { column = 'point', type = 'point' }, 20 | }), 21 | indexes = { 22 | { method = 'gist', column = 'point' }, 23 | } 24 | } 25 | 26 | themepark:add_table{ 27 | name = 'waterways', 28 | ids_type = 'way', 29 | geom = 'linestring', 30 | columns = themepark:columns({ 31 | { column = 'name', type = 'text' }, 32 | { column = 'waterway', type = 'text' }, 33 | }), 34 | } 35 | 36 | themepark:add_proc('area', function(object, data) 37 | if (object.tags.natural == 'water' or object.tags.waterway == 'dock' or object.tags.waterway == 'basin' or object.tags.waterway == 'reservoir') 38 | then 39 | local g_transform = object:as_area():transform(3857) 40 | local name = object.tags.name 41 | local a = { name = name, way_area = g_transform:area(), geom = g_transform } 42 | -- Only add points for water areas that need labels 43 | if name then 44 | a.point = g_transform:pole_of_inaccessibility() 45 | end 46 | themepark:add_debug_info(a, object.tags) 47 | themepark:insert('water', a) 48 | end 49 | end) 50 | 51 | themepark:add_proc('way', function(object, data) 52 | if (object.tags.waterway == 'river' 53 | or object.tags.waterway == 'canal' 54 | or object.tags.waterway == 'stream' 55 | or object.tags.waterway == 'drain' 56 | or object.tags.waterway == 'ditch' 57 | ) then 58 | local a = { name = object.tags.name, waterway = object.tags.waterway, 59 | geom = object:as_linestring() } 60 | themepark:add_debug_info(a, object.tags) 61 | themepark:insert('waterways', a) 62 | end 63 | end) 64 | --------------------------------------------------------------------------------