[
  {
    "path": ".gitignore",
    "content": "/public/static/css/mapbox-gl.*\n/public/static/index.html\n/public/static/javascript/*.elm.js*\n/public/static/javascript/mapbox-gl.*\n/source/api/geocode/*.tsv\n/source/api/geocode/index/\n/source/api/geocode/indexer/target/\n/source/api/geocode/searcher/target/\n/source/api/tile/target/\n/source/api/tile/database/*.mbtiles\n/source/client/elm-stuff\n/source/client/tests/elm-stuff\n/source/server/target\n.idea/\n.DS_Store\n"
  },
  {
    "path": ".gitmodules",
    "content": "[submodule \"source/api/graphhopper\"]\n\tpath = source/api/route\n\turl = https://github.com/atlasr-org/graphhopper\n\tbranch = master\n[submodule \"source/map-style\"]\n\tpath = source/map-style\n\turl = https://github.com/atlasr-org/osm-liberty\n"
  },
  {
    "path": "LICENSE",
    "content": "                                New BSD License\n\nCopyright © 2018, Ivan Enderlin.\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above copyright\n      notice, this list of conditions and the following disclaimer in the\n      documentation and/or other materials provided with the distribution.\n    * Neither the name of Atlasr nor the names of its contributors may be\n      used to endorse or promote products derived from this software without\n      specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\nARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE\nLIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\nCONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\nSUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\nINTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\nCONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\nARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\nPOSSIBILITY OF SUCH DAMAGE.\n\n"
  },
  {
    "path": "README.md",
    "content": "# 🌍 Atlasr\n\nAtlasr is a truly open-source and free map browser. The goal is threefold:\n\n  1. Learn about all the layers and components that make a map,\n  2. Provide a ready-to-use set of tools to build a 100% open-source,\n     free, and standalone map browser,\n  3. Provide an alternative service to the famous [Google Maps][GMaps]\n     or [Apple Maps][AMAps], with no ads, with respect to user privacy,\n     with 100% open-source and free data, and the top 10 features of\n     Google Maps.\n\nA map architecture is composed of the following components:\n\n  * **Map data**, A giant database containing all information about\n    roads, trails, cafés, railway stations, buildings etc. The biggest\n    major project is [OpenStreetMap][OSM],\n  * **Tile server**, A program that, given a specific region, reads\n    the map database and compiles those information in a certain\n    format like [`.mbtiles`][MBtiles] for instance. Each region of a\n    planet is named a _tile_. A tile is defined by a longitude, a\n    latitude, and a zoom (an altitude scale),\n  * **Map renderer**, A program that, given a set of tiles,\n    renders/draws a map, so each roads, buildings etc. finally come\n    alive at this step. A map renderer requires at least the following\n    components:\n      * **Tile decoder** to decode the tiles received from the tile\n        server,\n      * **Styles** to know how to draw the data from the map\n        (e.g. “roads must be blue with a white strike”),\n      * **Fonts** to render texts,\n      * **Icons** to represent some places with an image (like\n        hospitals, police stations, parcs etc.).\n  * **Geocoding**, A program that can find the longitude and the\n    latitude of a labellised element on earth, like a postal address,\n    a building name, or a river name for examples,\n  * **Routing**, A program that is able to find a path/route between\n    one or many points (longitude + latitude) given some constraints\n    (like the vehicule type, the road preference etc.). A route\n    preferably comes with descriptions, like “Turn right in 100m”,\n    “Follow A10 for 7km” etc.\n\nObviously, each component comes with thousands of details and\nconstraints. The previous list is a high overview of how it works.\n\n## Goals\n\nThe open-source map ecosystem is mature. Many projects already exist\nto address one component of the map architecture. However, a\nmainstream tool that combined all these projects, based on 100%\nopen-source and free data, is still missing. Atlasr aims to be the\nresponse to this problem.\n\nThe quality must be comparable with Google Maps or Apple Maps:\n\n  * Smooth and fast experience,\n  * Reliable data,\n  * Beautiful design for the map and the UI.\n\n## Roadmap\n\nThe main technologies are the following: [Rust] for the server, [Elm]\nfor the client, and [PostgreSQL]/[SQLite] for the databases.\n\nThe actual roadmap is the following:\n\n  * **Map data**, _all data comes from OpenStreetMap_,\n  * **Tile server** [`source/api/tile/`]:\n    * [x] Vector tiles are pre-computed by [OpenMapTiles][OMT], which\n          relies on OpenStreetMap.\n    * [x] Format is `.mbtiles`,\n    * [x] Solid and robust tile server.\n  * **Map renderer** [`source/map-style`]:\n    * [x] Use [Mapbox GL JS] to render the map,\n    * [x] Elm ports to Mapbox GL (JavaScript) for the Web UI,\n    * [x] Use [OSM Liberty] for the style, forked to remove the\n          dependencies to external services, and to use only “local”\n          data,\n    * [x] Icons/sprites,\n    * [ ] Fonts.\n  * **Geocoding** [`source/api/geocode`]:\n    * [x] Data are pre-computed by [OSM Names][OSMNames], which relies\n          on OpenStreetMap,\n    * [x] Transform the data to build an index for the search engine,\n    * [x] Custom and fast search engine to query the index, build with [Tantivy],\n    * [x] Solid and robust server.\n  * **Routing** [`source/api/route/`]:\n    * [x] Delegate all the works to [GraphHopper], only use the\n          open-source API. API to use:\n      * [x] Routing API,\n      * [ ] Isochrone API.\n  * **HTTP server** [`source/server/`]:\n    * [x] Fast and robust HTTP server between the client and all the API.\n  * **Client**/**Web UI** [`source/client`]:\n    * [x] Mobile-first,\n    * [x] Smooth and fast,\n    * [x] Search one or many positions (geocoding),\n    * [x] Search a route (routing),\n    * [ ] Search a route with constraints,\n    * [ ] Enhance data with Wikipedia (photos, descriptions, metadata etc.),\n    * [ ] All links are sharable,\n    * More features.\n    \n**Current focus**: The current hard work is to provide all map\ncomponents as local and standalone instances. Everything has been\naddressed except the fonts in the map renderer (yet).\n\n**Next focus**: Replace the top 10 features on Google Maps.\n\n### Screenshots\n\n  * **Map renderer**: The tiles, the style, the icons, everything\n    comes from Atlasr. No external service is used.\n\n    ![Map](./documentation/images/map.jpg)\n    \n  * **Geocoding** and **Routing**: Atlasr is able to geoencode 2\n    postal addresses, and find a route between the two:\n  \n    ![Geocoding and routing](./documentation/images/routing.jpg)\n\n## Usages/Installations\n\n[`just`] is required to run all the commands. Run `just --list` to get an overview of all the commands.\n\n  * **Tile server**:\n\n    ```sh\n    $ # Install API tile server.\n    $ just install-api-tile\n\n    $ # Run the tile server.\n    $ just run-api-tile\n    ```\n  \n  * **Geocoding**:\n\n    ```sh\n    $ # Download the data, install the indexer, and install the search engine.\n    $ just install-api-geocode\n\n    $ # Run the geocoding server.\n    $ just run-api-geocode-searcher\n    ```\n  \n  * **Routing**:\n  \n    ```sh\n    $ # Install the geocoding server.\n    $ just install-api-route\n\n    $ # Run the geocoding server.\n    $ just run-api-route\n    ```\n  \n  * **Client**/**Web UI**:\n  \n    ```sh\n    $ # Install the HTTP server for the client.\n    $ just install-server\n\n    $ # Run the HTTP server for the client.\n    $ just run-server\n\n    $ # Install the Web UI, its dependencies, and prepare the frontend.\n    $ just install-client\n\n    $ # Open the client.\n    $ just open\n    ```\n  \nEnjoy!\n\n## License\n\nThe entire project is under the BSD-3-Clause license. Please read the\n`LICENSE` file.\n\n[OSM]: https://openstreetmap.org/ \n[MBTiles]: https://github.com/mapbox/mbtiles-spec\n[GMaps]: https://google.com/maps\n[AMaps]: https://maps.apple.com/\n[Rust]: https://rust-lang.org/\n[Elm]: https://elm-lang.org/\n[PostgreSQL]: https://www.postgresql.org/\n[SQLite]: https://www.sqlite.org/\n[OMT]: https://openmaptiles.org/\n[Mapbox GL JS]: https://www.mapbox.com/mapbox-gl-js/api/\n[OSM Liberty]: https://github.com/atlasr-org/osm-liberty/\n[OSMNames]: http://osmnames.org/\n[Tantivy]: https://github.com/tantivy-search/tantivy/\n[GraphHopper]: https://www.graphhopper.com/\n[`just`]: https://github.com/casey/just/\n"
  },
  {
    "path": "justfile",
    "content": "# vim: filetype=just\n\nmapbox_gl_js_version = \"0.50.0\"\nserver_address = \"localhost:8889\"\ngeocode_api_address = \"localhost:8990\"\nroute_api_address = \"localhost:8989\"\ngeocode_data_planet = \"https://github.com/OSMNames/OSMNames/releases/download/v2.0.4/planet-latest_geonames.tsv.gz\"\ntile_api_address = \"127.0.0.1:8991\"\n\n# Open Atlasr in your favorite browser.\nopen: install\n\topen http://{{server_address}}\n\n# Install Atlasr!\ninstall: install-server install-api install-client\n\n# Test Atlasr.\ntest: test-server test-client\n\n# Uninstall Atlasr.\nuninstall: uninstall-server uninstall-api uninstall-client\n\n# Install the HTTP server.\ninstall-server:\n\tcd source/server && \\\n\t\tSERVER_ADDRESS={{server_address}} \\\n\t\tROUTE_API_ADDRESS={{route_api_address}} \\\n\t\tGEOCODE_API_ADDRESS={{geocode_api_address}} \\\n\t\tTILE_API_ADDRESS={{tile_api_address}} \\\n\t\tcargo build --release\n\n# Test the HTTP server.\ntest-server:\n\tcd source/server && cargo test\n\n# Uninstall the HTTP server.\nuninstall-server:\n\trm -rf source/server/Cargo.lock source/server/target\n\n# Run the HTTP server (will not exit).\nrun-server: install-server\n\tcd source/server && cargo run --release\n\n# Install all the APIs.\ninstall-api: install-api-geocode install-api-route install-api-tile\n\n# Uninstall all the APIs.\nuninstall-api: uninstall-api-geocode uninstall-api-route\n\n# Install the geocode API.\ninstall-api-geocode: install-api-geocode-data install-api-geocode-indexer install-api-geocode-searcher\n\n# Install/download data for the geocode API.\ninstall-api-geocode-data: install-api-geocode-indexer\n\tcd source/api/geocode && \\\n\t\tcurl -L {{geocode_data_planet}} > planet.tsv.gz && \\\n\t\tgzip -df planet.tsv.gz && \\\n\t\tmkdir -p index && \\\n\t\t./indexer/target/release/atlasr-api-geocode-indexer --source-file planet.tsv --index-directory index\n\n# Install the indexer for the geocode API.\ninstall-api-geocode-indexer:\n\tcd source/api/geocode/indexer && cargo build --release\n\n# Install the searcher for the geocode API.\ninstall-api-geocode-searcher:\n\tcd source/api/geocode/searcher && \\\n\t\tGEOCODE_API_ADDRESS={{geocode_api_address}} \\\n\t\tcargo build --release\n\nuninstall-api-geocode:\n\t# noop\n\nrun-api-geocode-searcher:\n\tcd source/api/geocode/searcher && cargo run --release\n\n# Install the route API (GraphHopper).\ninstall-api-route:\n\tgit submodule update --init source/api/route\n\n# Uninstall the route API (GraphHopper).\nuninstall-api-route:\n\t# noop\n\n# Run the route API (GraphHopper) for a particular PBF zone.\nrun-api-route map_file='europe_switzerland': install-api-route\n\tcd source/api/route && ./graphhopper.sh web {{map_file}}.pbf\n\n# Install the tile API.\ninstall-api-tile:\n\tcd source/api/tile && \\\n\t\tSERVER_ADDRESS={{server_address}} \\\n\t\tTILE_API_ADDRESS={{tile_api_address}} \\\n\t\tcargo build --release\n\n# Run the tile API.\nrun-api-tile:\n\tcd source/api/tile && cargo run --release\n\n# Uninstall the tile API.\nuninstall-api-tile:\n\t# noop\n\n#run-api-tile map_file='europe_switzerland': install-api-tile\n#\tcd source/api/tile && \\\n#\t\tcurl -L 'https://openmaptiles.com/download/WyJjOWUzNGM1NS04MDQxLTQ4MTMtYmUzMy0yNmFjMGUyN2I5MWIiLCItMSIsODcxM10.DsGknA.wk4qsZRjBSL8gQrp22h2CRpCyi4/osm-2017-07-03-v3.6.1-{{map_file}}.mbtiles?usage=open-source' > database/{{map_file}}.mbtiles\n\n# Install client.\ninstall-client: install-client-index install-client-application install-client-dependencies\n\n# Test client.\ntest-client: test-client-application\n\n# Uninstall client.\nuninstall-client: uninstall-client-index uninstall-client-application\n\n# Create a public `index.html` file.\nSED_INPLACE_OPTION = \"\\\"s@{MAP-PLACEHOLDER.svg}@`cat public/static/image/map-placeholder.svg | sed -E 's/^ +//g; s/\\\"/'\\\"'\\\"'/g; s/</%3c/g; s/>/%3e/g; s/\\\\#/%23/g' | tr -d \\\"\\\\n\\\"`@\\\"\"\ninstall-client-index:\n\t#!/bin/sh\n\tcp source/client/src/index.html public/static/index.html\n\tif [[ \"$OSTYPE\" == \"darwin\"* ]]; then\n\t\tsed -i '' {{SED_INPLACE_OPTION}}  public/static/index.html\n\telse\n\t\tsed -i {{SED_INPLACE_OPTION}} public/static/index.html\n\tfi\n\n# Remove the public `index.html` file.\nuninstall-client-index:\n\trm -f public/static/index.html\n\n# Compile the Elm application to JS.\ninstall-client-application:\n\tcd source/client && elm make src/Main.elm --optimize --output ../../public/static/javascript/application.elm.js\n\tcd public/static/javascript && \\\n\t\tuglifyjs application.elm.js \\\n\t\t\t--compress 'pure_funcs=\"F2,F3,F4,F5,F6,F7,F8,F9,A2,A3,A4,A5,A6,A7,A8,A9\",pure_getters,keep_fargs=false,unsafe_comps,unsafe' | \\\n\t\tuglifyjs \\\n\t\t\t--mangle \\\n\t\t\t--output=application.min.elm.js && \\\n\t\tgzip --best --stdout application.min.elm.js > application.min.elm.js.gz && \\\n\t\tbrotli --best --stdout application.min.elm.js > application.min.elm.js.br\n\n# Test the Elm application.\ntest-client-application:\n\tcd source/client && elm-test tests/unit/\n\n# Remove the Elm build artifact.\nuninstall-client-application:\n\trm -f public/static/javascript/application.elm.js\n\n# Install client dependencies.\ninstall-client-dependencies: install-mapbox-gl-js install-mapbox-gl-css\n\n# Uninstall client dependencies.\nuninstall-client-dependencies: uninstall-mapbox-gl-js uninstall-mapbox-gl-css\n\n# Install Mapbox GL JS, so JS and SourceMap.\ninstall-mapbox-gl-js:\n\tcd public/static/javascript/ && \\\n\t\tcurl -L https://api.tiles.mapbox.com/mapbox-gl-js/v{{mapbox_gl_js_version}}/mapbox-gl.js > mapbox-gl.js && \\\n\t\tcurl -L https://api.tiles.mapbox.com/mapbox-gl-js/v{{mapbox_gl_js_version}}/mapbox-gl.js.map > mapbox-gl.js.map && \\\n\t\tuglifyjs \\\n\t\t\t--compress \\\n\t\t\t--mangle \\\n\t\t\t--output=mapbox-gl.min.js mapbox-gl.js && \\\n\t\tgzip --best --stdout mapbox-gl.min.js > mapbox-gl.min.js.gz && \\\n\t\tbrotli --best --stdout mapbox-gl.min.js > mapbox-gl.min.js.br\n\n# Install Mapbox GL CSS\ninstall-mapbox-gl-css:\n\tcd public/static/css/ && \\\n\t\tcurl -L https://api.tiles.mapbox.com/mapbox-gl-js/v{{mapbox_gl_js_version}}/mapbox-gl.css > mapbox-gl.css \n\n# Uninstall Mapbox GL JS.\nuninstall-mapbox-gl-js:\n\trm public/static/javascript/mapbox-gl.js\n\trm public/static/javascript/mapbox-gl.js.map\n\n# Uninstall Mapbox GL CSS.\nuninstall-mapbox-gl-css:\n\trm public/static/css/mapbox-gl.css\n\n# Local Variables:\n# mode: makefile\n# End:\n"
  },
  {
    "path": "public/static/css/default.css",
    "content": ":root {\n    --brand-color        : hsla(221,  40%,  14%, 1  );\n    --alt-color-lighter  : hsla(  9,  93%, 100%, 1  );\n    --alt-color-light    : hsla(  9,  50%,  72%, 1  );\n    --alt-color          : hsla(  9,  93%,  72%, 1  );\n    --alt-color-dark     : hsla(  9,  93%,  25%,  .7);\n    --spacing            : 1rem;\n    --spacing-small      : calc(var(--spacing) / 2);\n    --spacing-large      : calc(var(--spacing) * 2);\n    --border-radius      : 2px;\n    --transition-duration: .1s;\n\n    --panel-width                   : 100vw;\n    --panel-height                  : 70vh;\n    --panel-min-width               : auto;\n    --panel-max-width               : auto;\n    --panel-unexpanded-translate-x  : 0;\n    --panel-unexpanded-translate-y  : -100%;\n    --panel-expanded-map-translate-x: 0;\n    --panel-expanded-map-translate-y: 35vh;\n}\n\n@media all and (min-width: 600px) {\n    :root {\n        --panel-width                   : 30vw;\n        --panel-height                  : 100vh;\n        --panel-min-width               : 300px;\n        --panel-max-width               : 500px;\n        --panel-unexpanded-translate-x  : -100%;\n        --panel-unexpanded-translate-y  : 0;\n        --panel-expanded-map-translate-x: 15vw;;\n        --panel-expanded-map-translate-y: 0;\n    }\n}\n\n@font-face {\n    font-family: Text;\n    src: url('../font/OpenSans-regular.woff') format('woff');\n    font-weight: normal;\n    font-style: normal;\n    font-display: swap;\n}\n\n* {\n    box-sizing: border-box !important;\n    margin: 0;\n    padding: 0;\n}\n\n[aria-hidden=\"true\"] {\n    display: none;\n}\n\nhtml {\n    font-size: 100%;\n}\n\nbody {\n    color: var(--brand-color);\n    font: 1.1rem/1.4rem Text, system-ui, sans-serif;\n    -webkit-font-smoothing: antialiased;\n    height: 100vh;\n}\n\n#body-background {\n    position: absolute;\n    object-fit: cover;\n    width: 100%;\n    height: 100%;\n}\n\nmain {\n    position: relative;\n    width: 100vw;\n    height: 100vh;\n    overflow: hidden;\n}\n\nnav > form[role=\"search\"] {\n    position: fixed;\n    top: var(--spacing);\n    padding: 0 var(--spacing);\n    width: var(--panel-width);\n    min-width: var(--panel-min-width);\n    max-width: var(--panel-max-width);\n    z-index: 2;\n}\n\nnav > section {\n    z-index: 1;\n    position: absolute;\n    width: var(--panel-width);\n    min-width: var(--panel-min-width);\n    max-width: var(--panel-max-width);\n    height: var(--panel-height);\n    padding: var(--spacing);\n    display: flex;\n    flex-direction: column;\n    background: var(--alt-color);\n\n    transform:\n        translateX(var(--panel-unexpanded-translate-x))\n        translateY(var(--panel-unexpanded-translate-y));\n    transition: var(--transition-duration) transform ease-in-out;\n}\n\n    nav[aria-expanded=\"true\"] > section {\n        box-shadow: 3px 0 var(--spacing) var(--alt-color-dark);\n        transform: translateX(0) translateY(0);\n    }\n\narticle {\n    position: absolute !important;\n    width: 100vw;\n    height: 100vh;\n}\n\n    article > .mapboxgl-canvas-container {\n        transform: translateX(0) translateY(0);\n        transition: var(--transition-duration) transform ease-in-out;\n    }\n\n    nav[aria-expanded=\"true\"] ~ article > .mapboxgl-canvas-container {\n        transform:\n            translateX(var(--panel-expanded-map-translate-x))\n            translateY(var(--panel-expanded-map-translate-y));\n    }\n\nfooter {\n    position: fixed;\n    bottom: var(--spacing);\n    right: var(--spacing);\n    color: var(--alt-color);\n    font-size: 1.1em;\n    font-weight: bold;\n    font-variant: all-small-caps;\n    letter-spacing: .1em;\n    text-shadow:\n        var(--alt-color-lighter)  0    2px,\n        var(--alt-color-lighter)  0   -2px,\n        var(--alt-color-lighter)  2px  0,\n        var(--alt-color-lighter) -2px  0;\n}\n\n@media all and (min-width: 600px) {\n    footer {\n        right: auto;\n        left: 50%;\n        transform: translateX(-50%);\n    }\n}\n\ninput {\n    all: unset;\n    -moz-appearance: none;\n    -webkit-appearance: none;\n    appearance: none;\n    width: 100%;\n    padding: var(--spacing-small) var(--spacing);\n    line-height: var(--spacing-large);\n    border: 1px var(--alt-color-light) solid;\n    border-radius: var(--border-radius);\n    background: var(--alt-color-lighter);\n}\n\n#map {\n    font: inherit;\n}\n\n    #map .mapboxgl-ctrl-group {\n        border: 1px var(--alt-color-light) solid;\n        box-shadow: unset;\n    }\n\n    #map .mapboxgl-ctrl-icon {\n        border-color: var(--alt-color-light);\n    }\n\n    #map .mapboxgl-ctrl-attrib {\n        font-size: .6em;\n        background: hsla(0, 0%, 100%, .8);\n    }\n"
  },
  {
    "path": "public/static/image/README.md",
    "content": "# `map-placeholder.svg`\n\n1. Raster a part of the map,\n2. Install [`primitive`](https://github.com/fogleman/primitive),\n3. `primitive -m 1 -n 60 -i map-rasterized.png -o map-triangles.svg`,\n4. Optimise it with [SVGOMG](https://jakearchibald.github.io/svgomg/),\n5. Put the paths (only) into the following template:\n   ```xml\n   <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"1024\" height=\"540\" version=\"1\">\n     <filter id=\"blur\">\n       <feGaussianBlur stdDeviation=\"12\" />\n     </filter>\n     <g filter=\"url(#blur)\">\n       …\n     </g>\n   </svg>\n   ```\n"
  },
  {
    "path": "public/static/javascript/main.js",
    "content": "(\n    () => {\n        const atlasr = Elm.Atlasr.Main.init({\n            node: document.body,\n            flags: 1\n        });\n        const LAYER_ROUTE_MARKERS_PREFIX = 'markers-';\n        let   map                        = null;\n        let   layer_route_markers        = '';\n        let   markers                    = [];\n\n        atlasr.ports.mapboxgl_create_map.subscribe(\n            ([id, options]) => {\n                mapboxgl.accessToken = 'undefined';\n\n                map = new mapboxgl.Map({\n                    container         : id,\n                    style             : options.style,\n                    center            : options.center,\n                    zoom              : options.zoom,\n                    hash              : options.hash,\n                    pitchWithRotate   : options.pitchWithRotate,\n                    attributionControl: options.attributionControl,\n                    dragRotate        : options.dragRotate,\n                    dragPan           : options.dragPan,\n                    keyboard          : options.keyboard,\n                    doubleClickZoom   : options.doubleClickZoom,\n                    touchZoomRotate   : options.touchZoomRotate,\n                    trackResize       : options.trackResize,\n                    renderWorldCopies : options.renderWorldCopies\n                });\n                map.addControl(\n                    new mapboxgl.GeolocateControl(\n                        {\n                            positionOptions: {\n                                enableHighAccuracy: true\n                            },\n                            trackUserLocation: true\n                        }\n                    ),\n                    'bottom-right'\n                );\n                map.addControl(\n                    new mapboxgl.NavigationControl(),\n                    'bottom-right'\n                );\n            }\n        );\n        atlasr.ports.mapboxgl_fly_to.subscribe(\n            (cameraOptions) => {\n                map.flyTo(cameraOptions);\n            }\n        );\n        atlasr.ports.mapboxgl_add_markers.subscribe(\n            (positions) => {\n                for (let [longitude, latitude] of positions) {\n                    markers.push(\n                        new mapboxgl.Marker()\n                            .setLngLat([longitude, latitude])\n                            .addTo(map)\n                    );\n                }\n            }\n        );\n        atlasr.ports.mapboxgl_remove_markers.subscribe(\n            () => {\n                for (let marker of markers) {\n                    marker.remove();\n                }\n\n                markers = [];\n\n                if (undefined !== map.getLayer(layer_route_markers)) {\n                    map.removeLayer(layer_route_markers);\n                }\n            }\n        );\n        atlasr.ports.mapboxgl_connect_markers.subscribe(\n            (positions) => {\n                if (1 < positions.length) {\n                    layer_route_markers = LAYER_ROUTE_MARKERS_PREFIX + guid();\n\n                    map.addLayer({\n                        'id': layer_route_markers,\n                        'type': 'line',\n                        'source': {\n                            'type': 'geojson',\n                            'data': {\n                                'type': 'Feature',\n                                'properties': {},\n                                'geometry': {\n                                    'type': 'LineString',\n                                    'coordinates': positions\n                                }\n                            }\n                        },\n                        'layout': {\n                            'line-join': 'round',\n                            'line-cap': 'round'\n                        },\n                        'paint': {\n                            'line-color': '#00b3fd',\n                            'line-width': 8\n                        }\n                    });\n                }\n            }\n        );\n\n        function guid() {\n            const s4 = () => {\n                return Math.floor((1 + Math.random()) * 0x10000)\n                    .toString(16)\n                    .substring(1);\n            };\n\n            return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();\n        }\n    }\n)();\n"
  },
  {
    "path": "source/api/geocode/indexer/Cargo.toml",
    "content": "[package]\nname = \"atlasr-api-geocode-indexer\"\nversion = \"0.1.0\"\nauthors = [\"Ivan Enderlin <ivan.enderlin@hoa-project.net>\"]\n\n[dependencies]\ntantivy = \"^0.6\"\nclap = \"^2.31.2\"\nfailure = \"^0.1.1\"\ncsv = \"^1.0.0\"\nserde = \"^1.0.66\"\nserde_derive = \"^1.0.66\""
  },
  {
    "path": "source/api/geocode/indexer/src/main.rs",
    "content": "extern crate failure;\nextern crate clap;\nextern crate csv;\n#[macro_use] extern crate serde_derive;\nextern crate tantivy;\n\nuse failure::{\n    Error,\n    ResultExt\n};\nuse clap::{App, Arg};\nuse tantivy::{\n    Index,\n    IndexWriter,\n    directory,\n    schema::*\n};\nuse std::fs::File;\nuse std::io::{\n    self,\n    BufReader,\n    Write\n};\n\n/// [Definition of these fields](http://osmnames.readthedocs.io/en/latest/introduction.html#output-format).\n#[derive(Debug, Deserialize)]\nstruct Record {\n    name: String,\n    alternative_names: Option<String>,\n    osm_type: String,\n    osm_id: u64,\n    class: String,\n    #[serde(rename = \"type\")]\n    object_type: String,\n    #[serde(rename = \"lon\")]\n    longitude: Option<f64>,\n    #[serde(rename = \"lat\")]\n    latitude: Option<f64>,\n    place_rank: Option<u64>,\n    importance: Option<f64>,\n    street: Option<String>,\n    city: Option<String>,\n    county: Option<String>,\n    state: Option<String>,\n    country: Option<String>,\n    country_code: Option<String>,\n    display_name: String,\n    #[serde(rename = \"west\")]\n    bbox_west: Option<f64>,\n    #[serde(rename = \"south\")]\n    bbox_south: Option<f64>,\n    #[serde(rename = \"east\")]\n    bbox_east: Option<f64>,\n    #[serde(rename = \"north\")]\n    bbox_north: Option<f64>,\n    wikidata: Option<String>,\n    wikipedia: Option<String>,\n    housenumbers: Option<String>\n}\n\nimpl Record {\n    fn indexable(&self) -> bool {\n        if self.longitude.is_none() || self.latitude.is_none() {\n            return false;\n        }\n\n        match (self.class.as_str(), self.object_type.as_str()) {\n            (\"boundary\", _) |\n            (\"highway\", \"residential\") => {\n                return true;\n            },\n\n            (_, _) => { }\n        }\n\n        false\n    }\n}\n\nfn create_schema() -> Schema {\n    let mut schema_builder = SchemaBuilder::default();\n\n    schema_builder.add_text_field(\"display_name\", TEXT | STORED);\n    schema_builder.add_text_field(\"class\", STORED);\n    schema_builder.add_text_field(\"type\", STORED);\n    schema_builder.add_text_field(\"longitude\", STORED);\n    schema_builder.add_text_field(\"latitude\", STORED);\n\n    schema_builder.build()\n}\n\nfn create_index(index_directory: directory::MmapDirectory, schema: &Schema) -> Index {\n    Index::create(index_directory, schema.clone()).unwrap()\n}\n\nfn index_record(index_writer: &mut IndexWriter, schema: &Schema, record: Record) -> tantivy::Result<()> {\n    if ! record.indexable() {\n        return Ok(());\n    }\n\n    let field_display_name = schema.get_field(\"display_name\").unwrap();\n    let field_class = schema.get_field(\"class\").unwrap();\n    let field_type = schema.get_field(\"type\").unwrap();\n    let field_longitude = schema.get_field(\"longitude\").unwrap();\n    let field_latitude = schema.get_field(\"latitude\").unwrap();\n\n    let mut document = Document::default();\n    document.add_text(field_display_name, &record.display_name);\n    document.add_text(field_class, &record.class);\n    document.add_text(field_type, &record.object_type);\n    document.add_text(field_longitude, &record.longitude.unwrap().to_string());\n    document.add_text(field_latitude, &record.latitude.unwrap().to_string());\n\n    index_writer.add_document(document);\n\n    Ok(())\n}\n\nfn main() -> Result<(), Error> {\n    let matches =\n        App::new(\"atlasr-api-geocode-indexer\")\n            .version(env!(\"CARGO_PKG_VERSION\"))\n            .about(\"Atlasr: Indexer for the geocode API.\")\n            .author(\"Ivan Enderlin\")\n            .arg(\n                Arg::with_name(\"source-file\")\n                    .help(\"File containing the data to index, must be a `.tsv` file.\")\n                    .short(\"s\")\n                    .long(\"source-file\")\n                    .value_name(\"SOURCE_FILE\")\n                    .takes_value(true)\n                    .required(true)\n            )\n            .arg(\n                Arg::with_name(\"index-directory\")\n                    .help(\"Directory that will contain the index.\")\n                    .short(\"i\")\n                    .long(\"index-directory\")\n                    .value_name(\"INDEX_DIRECTORY\")\n                    .takes_value(true)\n                    .required(true)\n            )\n            .get_matches();\n\n    let source_file_name = matches.value_of(\"source-file\").unwrap();\n    let source_file = File::open(source_file_name).context(\"Cannot open the given file.\")?;\n    let source_buffer = BufReader::new(source_file);\n\n    let mut source_reader =\n        csv::ReaderBuilder::new()\n            .delimiter(b'\\t')\n            .double_quote(false)\n            .flexible(true)\n            .from_reader(source_buffer);\n\n    let index_directory = matches.value_of(\"index-directory\").unwrap();\n    let schema = create_schema();\n    let index = create_index(directory::MmapDirectory::open(index_directory)?, &schema);\n    let mut index_writer = index.writer(50_000_000)?;\n    let stdout = io::stdout();\n    let mut handle = stdout.lock();\n\n    for record in source_reader.deserialize() {\n        let record: Record = record?;\n\n        handle.write(b\"~~> \").unwrap();\n        handle.write(record.name.as_bytes()).unwrap();\n        handle.write(b\"\\n\").unwrap();\n\n        index_record(&mut index_writer, &schema, record)?;\n    }\n\n    index_writer.commit()?;\n\n    Ok(())\n}\n"
  },
  {
    "path": "source/api/geocode/searcher/Cargo.toml",
    "content": "[package]\nname = \"atlasr-api-geocode-searcher\"\nversion = \"0.1.0\"\nauthors = [\"Ivan Enderlin <ivan.enderlin@hoa-project.net>\"]\n\n[dependencies]\ntantivy = \"^0.6\"\nactix-web = \"^0.6.10\""
  },
  {
    "path": "source/api/geocode/searcher/src/main.rs",
    "content": "extern crate actix_web;\nextern crate tantivy;\n\nuse actix_web::{\n    App,\n    HttpRequest,\n    HttpResponse,\n    http::Method,\n    server\n};\nuse tantivy::{\n    Index,\n    collector::TopCollector,\n    directory,\n    query::QueryParser\n};\nuse std::cmp::min;\n\nconst GEOCODE_API_ADDRESS: &'static str = env!(\"GEOCODE_API_ADDRESS\");\n\nstruct SearchState {\n    index: Index,\n    query_parser: QueryParser\n}\n\nfn serve_search(request: HttpRequest<SearchState>) -> HttpResponse {\n    let term: &str;\n\n    match request.query().get(\"term\") {\n        Some(value) => term = value,\n        None => return HttpResponse::BadRequest().reason(\"Query `term` is missing\").finish()\n    }\n\n    let limit: u8 = match request.query().get(\"limit\") {\n        Some(value) => min(value.parse().unwrap_or(3), 10),\n        None => 3\n    };\n\n    let index = &request.state().index;\n    let query_parser = &request.state().query_parser;\n\n    let schema = index.schema();\n    let searcher = index.searcher();\n\n    let query = match query_parser.parse_query(term) {\n        Ok(value) => {\n            value\n        },\n\n        Err(_) => {\n            return HttpResponse::InternalServerError().finish();\n        }\n    };\n\n    let mut top_collector = TopCollector::with_limit(limit as usize);\n\n    match searcher.search(&*query, &mut top_collector) {\n        Err(_) => {\n            return HttpResponse::InternalServerError().finish();\n        }\n\n        _ => { }\n    }\n\n    let mut response = String::from(\"[\");\n\n    for document in top_collector.docs() {\n        let retrieved_document = match searcher.doc(&document) {\n            Ok(value) => {\n                value\n            },\n\n            Err(_) => {\n                return HttpResponse::InternalServerError().finish();\n            }\n        };\n\n        response.push_str(&schema.to_json(&retrieved_document));\n        response.push_str(\",\");\n    }\n\n    if response.len() > 1 {\n        response.pop();\n    }\n\n    response.push_str(\"]\");\n\n    HttpResponse::Ok().content_type(\"application/json\").body(response)\n}\n\nfn main() {\n    server\n        ::new(\n            || {\n                App\n                    ::with_state(\n                        {\n                            let index = Index::open(directory::MmapDirectory::open(\"../index\").unwrap()).unwrap();\n                            index.load_searchers().unwrap();\n\n                            let schema = index.schema();\n                            let query_parser = QueryParser::for_index(\n                                &index,\n                                vec![\n                                    schema.get_field(\"display_name\").unwrap()\n                                ]\n                            );\n\n                            SearchState {\n                                index: index,\n                                query_parser: query_parser\n                            }\n                        }\n                    )\n                    .resource(\n                        \"/search\", // ?term=…&limit=…\n                        |resource| {\n                            resource.method(Method::GET).f(serve_search)\n                        }\n                    )\n            }\n        )\n        .bind(GEOCODE_API_ADDRESS)\n        .expect(&format!(\"Cannot bind the server to {}.\", GEOCODE_API_ADDRESS))\n        .shutdown_timeout(30)\n        .run();\n}\n"
  },
  {
    "path": "source/api/tile/Cargo.toml",
    "content": "[package]\nname = \"atlasr-api-tile\"\nversion = \"0.1.0\"\nauthors = [\"Ivan Enderlin <ivan.enderlin@hoa-project.net>\"]\n\n[dependencies]\nwarp = \"^0.1.9\"\ndiesel = { version = \"^1.3.3\", features = [\"sqlite\"] }\nr2d2 = \"^0.8.3\"\nr2d2-diesel = \"^1.0.0\"\nserde_json = \"^1.0.0\""
  },
  {
    "path": "source/api/tile/database/.keep",
    "content": ""
  },
  {
    "path": "source/api/tile/diesel.toml",
    "content": "[print_schema]\nfile = \"src/schema.rs\"\n"
  },
  {
    "path": "source/api/tile/src/main.rs",
    "content": "#[macro_use]\nextern crate warp;\n#[macro_use]\nextern crate diesel;\nextern crate r2d2;\nextern crate r2d2_diesel;\nextern crate serde_json as json;\n\nuse warp::Filter;\nuse warp::http;\nuse diesel::prelude::*;\nuse r2d2_diesel::ConnectionManager;\nuse std::net::SocketAddr;\nuse std::str::FromStr;\n\nmod schema;\nmod models;\n\nconst SERVER_ADDRESS: &'static str = env!(\"SERVER_ADDRESS\");\nconst TILE_API_ADDRESS: &'static str = env!(\"TILE_API_ADDRESS\");\n\nfn not_found() -> http::Result<http::Response<Vec<u8>>> {\n    http::Response::builder()\n        .status(http::StatusCode::NOT_FOUND)\n        .header(\"content-type\", \"text/plain; charset=utf-8\")\n        .body(vec![])\n}\n\nfn error() -> http::Result<http::Response<String>> {\n    http::Response::builder()\n        .status(http::StatusCode::NOT_FOUND)\n        .header(\"content-type\", \"text/plain; charset=utf-8\")\n        .body(\"error\".to_string())\n}\n\nfn main() {\n    let manager = ConnectionManager::<SqliteConnection>::new(\"database/europe_switzerland.mbtiles\");\n    let pool =\n        r2d2::Pool::builder()\n            .max_size(15)\n            .build(manager)\n            .expect(\"Failed to create pool.\");\n\n    let pool1 = pool.clone();\n    let pool2 = pool.clone();\n\n    // `GET /<zoom_level>/<tile_column>/<tile_row>/`\n    let tiles =\n        path!(u8 / u16 / u16)\n            .map(\n                move |z, x, y| {\n                    match pool1.get() {\n                        Ok(connection) => {\n                            use schema::tiles::dsl::*;\n                            use models::Tiles;\n\n                            let x = x as u32;\n                            let y = y as u32;\n                            let z = z as u32;\n\n                            let y = 2u32.pow(z) - 1 - y;\n\n                            let tile =\n                                tiles\n                                    .filter(zoom_level.eq(z as i32))\n                                    .filter(tile_column.eq(x as i32))\n                                    .filter(tile_row.eq(y as i32))\n                                    .first::<Tiles>(&*connection);\n\n                            match tile {\n                                Ok(tile) => {\n                                    http::Response::builder()\n                                        .header(\"content-type\", \"application/x-protobuf\")\n                                        .header(\"content-encoding\", \"gzip\")\n                                        .body(tile.tile_data)\n                                },\n\n                                Err(_) => {\n                                    not_found()\n                                }\n                            }\n                        },\n\n                        Err(_) => {\n                            not_found()\n                        }\n                    }\n                }\n            );\n\n    // `GET /metadata.json`\n    let metadata =\n        path!(\"metadata.json\")\n            .map(\n                move || {\n                    match pool2.get() {\n                        Ok(connection) => {\n                            use schema::metadata::dsl::*;\n                            use models::Metadata;\n\n                            match metadata.load::<Metadata>(&*connection) {\n                                Ok(all_metadata) => {\n                                    let mut output = json::map::Map::with_capacity(all_metadata.len());\n\n                                    for metadatum in all_metadata {\n                                        let v = metadatum.value;\n\n                                        let map_value = match metadatum.name.as_ref() {\n                                            \"bounds\" | \"center\" => {\n                                                Ok(\n                                                    json::Value::Array(\n                                                        v\n                                                            .splitn(4, ',')\n                                                            .map(\n                                                                |p| {\n                                                                    json::Value::Number(\n                                                                        json::Number::from_str(p).unwrap_or_else(|_| json::Number::from_f64(0.0).unwrap())\n                                                                    )\n                                                                }\n                                                            )\n                                                            .collect()\n                                                    )\n                                                )\n                                            },\n\n                                            \"json\" => {\n                                                if let Ok(json::Value::Object(meta_metadata)) = json::from_str(&v) {\n                                                    output.extend(meta_metadata);\n                                                }\n\n                                                Err(())\n                                            },\n\n                                            _ => Ok(json::Value::String(v))\n                                        };\n\n                                        if let Ok(map_value) = map_value {\n                                            output.insert(metadatum.name, map_value);\n                                        }\n                                    }\n\n                                    if !output.contains_key(\"profile\") {\n                                        output.insert(\"profile\".to_string(), json::Value::String(\"mercator\".to_string()));\n                                    }\n\n                                    if !output.contains_key(\"scale\") {\n                                        output.insert(\"scale\".to_string(), json::Value::Number(json::Number::from_f64(1.0).unwrap()));\n                                    }\n\n                                    if !output.contains_key(\"tilejson\") {\n                                        output.insert(\"tilejson\".to_string(), json::Value::String(\"2.0.0\".to_string()));\n                                    }\n\n                                    if !output.contains_key(\"tiles\") {\n                                        output.insert(\n                                            \"tiles\".to_string(),\n                                            json::Value::Array(vec![\n                                                json::Value::String(\n                                                    format!(\"http://{}/api/tile/{{z}}/{{x}}/{{y}}\", SERVER_ADDRESS)\n                                                )\n                                            ])\n                                        );\n                                    }\n\n                                    http::Response::builder()\n                                        .header(\"content-type\", \"application/json\")\n                                        .body(json::to_string(&output).unwrap())\n                                },\n\n                                Err(_) => error()\n                            }\n                        },\n\n                        Err(_) => error()\n                    }\n                }\n            );\n\n    let routes = tiles.or(metadata);\n\n    warp::serve(routes)\n        .run(TILE_API_ADDRESS.parse::<SocketAddr>().expect(&format!(\"Cannot bind the server to {}\", TILE_API_ADDRESS)));\n}\n"
  },
  {
    "path": "source/api/tile/src/models.rs",
    "content": "#[derive(Debug, Queryable)]\npub struct Tiles {\n    pub zoom_level: i32,\n    pub tile_column: i32,\n    pub tile_row: i32,\n    pub tile_data: Vec<u8>,\n}\n\n#[derive(Debug, Queryable)]\npub struct Metadata {\n    pub name: String,\n    pub value: String\n}\n"
  },
  {
    "path": "source/api/tile/src/schema.rs",
    "content": "table! {\n    tiles (zoom_level, tile_column, tile_row) {\n        zoom_level -> Integer,\n        tile_column -> Integer,\n        tile_row -> Integer,\n        tile_data -> Binary,\n    }\n}\n\ntable! {\n    metadata (name, value) {\n        name -> Text,\n        value -> Text,\n    }\n}\n"
  },
  {
    "path": "source/client/elm.json",
    "content": "{\n    \"type\": \"application\",\n    \"source-directories\": [\n        \"src\"\n    ],\n    \"elm-version\": \"0.19.0\",\n    \"dependencies\": {\n        \"direct\": {\n            \"elm/browser\": \"1.0.1\",\n            \"elm/core\": \"1.0.0\",\n            \"elm/html\": \"1.0.0\",\n            \"elm/http\": \"1.0.0\",\n            \"elm/json\": \"1.0.0\",\n            \"elm/url\": \"1.0.0\",\n            \"fapian/elm-html-aria\": \"1.4.0\"\n        },\n        \"indirect\": {\n            \"elm/time\": \"1.0.0\",\n            \"elm/virtual-dom\": \"1.0.2\"\n        }\n    },\n    \"test-dependencies\": {\n        \"direct\": {},\n        \"indirect\": {}\n    }\n}"
  },
  {
    "path": "source/client/src/Atlasr/Geocode.elm",
    "content": "module Atlasr.Geocode exposing (Geocode, toGeocodes)\n\nimport Atlasr.Position exposing (NamedPosition, defaultNamedPosition)\nimport Http\nimport Json.Decode\nimport Task\n\n\ntype alias Geocode =\n    { label : String\n    , longitude : String\n    , latitude : String\n    }\n\n\n{-| Geocode a list of position names.\n-}\ntoGeocodes : (Result Http.Error (List (Maybe Geocode)) -> msg) -> List NamedPosition -> Cmd msg\ntoGeocodes outputType positionsToGeocode =\n    let\n        ( defaultName, defaultPosition ) =\n            defaultNamedPosition\n\n        tasks =\n            List.map\n                (\\( positionName, position ) ->\n                    if positionName /= defaultName then\n                        if position == defaultPosition then\n                            positionToGeocodeRequest positionName\n                                |> Http.toTask\n                                |> Task.map (\\geocode -> Just geocode)\n\n                        else\n                            let\n                                ( longitude, latitude ) =\n                                    position\n\n                                geocode =\n                                    { label = positionName\n                                    , longitude = String.fromFloat longitude\n                                    , latitude = String.fromFloat latitude\n                                    }\n                            in\n                            Task.succeed (Just geocode)\n\n                    else\n                        Task.succeed Nothing\n                )\n                positionsToGeocode\n    in\n    Task.attempt outputType <| Task.sequence tasks\n\n\n{-| Create an HTTP request to geocode a position.\n-}\npositionToGeocodeRequest : String -> Http.Request Geocode\npositionToGeocodeRequest positionName =\n    let\n        url =\n            \"/api/geocode?term=\" ++ positionName ++ \"&limit=1\"\n    in\n    Http.get url decodeGeocode\n\n\n{-| Decoder for the geocode payload from the HTTP service.\n-}\ndecodeGeocode : Json.Decode.Decoder Geocode\ndecodeGeocode =\n    Json.Decode.at [ \"0\" ]\n        (Json.Decode.map3\n            Geocode\n            (Json.Decode.at [ \"display_name\", \"0\" ] Json.Decode.string)\n            (Json.Decode.at [ \"longitude\", \"0\" ] Json.Decode.string)\n            (Json.Decode.at [ \"latitude\", \"0\" ] Json.Decode.string)\n        )\n"
  },
  {
    "path": "source/client/src/Atlasr/Map.elm",
    "content": "module Atlasr.Map exposing (addMarkers, connectMarkers, create, flyTo, removeMarkers)\n\nimport Atlasr.MapboxGL.Options as Options\nimport Atlasr.MapboxGL.Ports exposing (..)\nimport Atlasr.Position exposing (Position)\n\n\n{-| Create a map with an ID and some options.\n-}\ncreate : String -> Options.Map -> Cmd msg\ncreate id options =\n    mapboxgl_create_map ( id, options )\n\n\n{-| Move the camera to a specific position.\n-}\nflyTo : Position -> Cmd msg\nflyTo position =\n    mapboxgl_fly_to { center = position }\n\n\n{-| Add markers to certain positions, and connect them.\n-}\naddMarkers : List Position -> Cmd msg\naddMarkers positions =\n    mapboxgl_add_markers positions\n\n\n{-| Remove all markers.\n-}\nremoveMarkers : Cmd msg\nremoveMarkers =\n    mapboxgl_remove_markers ()\n\n\n{-| Draw lines between points/positions.\n-}\nconnectMarkers : List Position -> Cmd msg\nconnectMarkers positions =\n    mapboxgl_connect_markers positions\n"
  },
  {
    "path": "source/client/src/Atlasr/MapboxGL/Options.elm",
    "content": "module Atlasr.MapboxGL.Options exposing (Camera, Map, default)\n\nimport Atlasr.Position exposing (Position)\n\n\ntype alias Map =\n    { style : String\n    , center : Position\n    , zoom : Int\n    , hash : Bool\n    , pitchWithRotate : Bool\n    , attributionControl : Bool\n    , dragRotate : Bool\n    , dragPan : Bool\n    , keyboard : Bool\n    , doubleClickZoom : Bool\n    , touchZoomRotate : Bool\n    , trackResize : Bool\n    , renderWorldCopies : Bool\n    }\n\n\ndefault : Map\ndefault =\n    { style = \"/static/map-style/style.json\"\n    , center = ( 2.294504285127, 48.858262790681 )\n    , zoom = 15\n    , hash = True\n    , pitchWithRotate = True\n    , attributionControl = True\n    , dragRotate = True\n    , dragPan = True\n    , keyboard = True\n    , doubleClickZoom = True\n    , touchZoomRotate = True\n    , trackResize = True\n    , renderWorldCopies = True\n    }\n\n\ntype alias Camera =\n    { center : Position }\n"
  },
  {
    "path": "source/client/src/Atlasr/MapboxGL/Ports.elm",
    "content": "port module Atlasr.MapboxGL.Ports exposing (mapboxgl_add_markers, mapboxgl_connect_markers, mapboxgl_create_map, mapboxgl_fly_to, mapboxgl_remove_markers)\n\nimport Atlasr.MapboxGL.Options as Options\nimport Atlasr.Position exposing (Position)\n\n\nport mapboxgl_create_map : ( String, Options.Map ) -> Cmd msg\n\n\nport mapboxgl_fly_to : Options.Camera -> Cmd msg\n\n\nport mapboxgl_add_markers : List Position -> Cmd msg\n\n\nport mapboxgl_remove_markers : () -> Cmd msg\n\n\nport mapboxgl_connect_markers : List Position -> Cmd msg\n"
  },
  {
    "path": "source/client/src/Atlasr/Position.elm",
    "content": "module Atlasr.Position exposing (Latitude, Longitude, NamedPosition, Position, defaultName, defaultNamedPosition, defaultPosition, extractLatitude, extractLongitude)\n\n\ntype alias Longitude =\n    Float\n\n\ntype alias Latitude =\n    Float\n\n\ntype alias Position =\n    ( Longitude, Latitude )\n\n\n{-| Allocate a default position.\n-}\ndefaultPosition : Position\ndefaultPosition =\n    ( 0.0, 0.0 )\n\n\n{-| Extract the longitude of a position.\n-}\nextractLongitude : Position -> Float\nextractLongitude ( longitude, _ ) =\n    longitude\n\n\n{-| Extract the latitude of a position.\n-}\nextractLatitude : Position -> Float\nextractLatitude ( _, latitude ) =\n    latitude\n\n\ntype alias NamedPosition =\n    ( String, Position )\n\n\n{-| Allocate a default position name.\n-}\ndefaultName : String\ndefaultName =\n    \"\"\n\n\n{-| Allocate a default named position.\n-}\ndefaultNamedPosition : NamedPosition\ndefaultNamedPosition =\n    ( defaultName, defaultPosition )\n"
  },
  {
    "path": "source/client/src/Atlasr/Route.elm",
    "content": "module Atlasr.Route exposing (Route, toRoute)\n\nimport Atlasr.Position exposing (Position)\nimport Http\nimport Json.Decode\nimport Task\nimport Url.Builder exposing (absolute, string)\n\n\ntype alias Route =\n    { points : List Position\n    }\n\n\ntype alias RawRoute =\n    { points : List Point\n    }\n\n\ntype alias Point =\n    { longitude : Float\n    , latitude : Float\n    }\n\n\nemptyRoute : Route\nemptyRoute =\n    { points = [] }\n\n\n{-| Get a route from a list of position.\n-}\ntoRoute : (Result Http.Error Route -> msg) -> List Position -> Cmd msg\ntoRoute outputType positions =\n    let\n        task =\n            if List.length positions <= 1 then\n                Task.succeed emptyRoute\n\n            else\n                positionsToRouteRequest positions\n                    |> Http.toTask\n                    |> Task.map\n                        (\\route ->\n                            Route\n                                (List.map\n                                    (\\point ->\n                                        ( point.longitude, point.latitude )\n                                    )\n                                    route.points\n                                )\n                        )\n    in\n    Task.attempt outputType task\n\n\n{-| Create an HTTP request to get the route between positions.\n-}\npositionsToRouteRequest : List Position -> Http.Request RawRoute\npositionsToRouteRequest positions =\n    let\n        url =\n            absolute\n                [ \"api/route\" ]\n                ([ string \"points_encoded\" \"false\"\n                 , string \"vehicle\" \"car\"\n                 ]\n                    ++ List.map\n                        (\\position ->\n                            let\n                                ( longitude, latitude ) =\n                                    position\n                            in\n                            string \"point\" (String.fromFloat latitude ++ \",\" ++ String.fromFloat longitude)\n                        )\n                        positions\n                )\n    in\n    Http.get url decodeRoute\n\n\n{-| Decoder for the route payload from the HTTP service.\n-}\ndecodeRoute : Json.Decode.Decoder RawRoute\ndecodeRoute =\n    Json.Decode.at [ \"paths\", \"0\" ]\n        (Json.Decode.map\n            RawRoute\n            (Json.Decode.at [ \"points\", \"coordinates\" ]\n                (Json.Decode.list\n                    (Json.Decode.map2\n                        Point\n                        (Json.Decode.field \"0\" Json.Decode.float)\n                        (Json.Decode.field \"1\" Json.Decode.float)\n                    )\n                )\n            )\n        )\n"
  },
  {
    "path": "source/client/src/Main.elm",
    "content": "module Atlasr.Main exposing (Model, Msg(..), init, main, subscriptions, update, view)\n\nimport Array exposing (Array)\nimport Atlasr.Geocode as Geocode\nimport Atlasr.Map as Map\nimport Atlasr.MapboxGL.Options as MapOptions\nimport Atlasr.Position as Position exposing (NamedPosition, Position)\nimport Atlasr.Route as Route\nimport Browser\nimport Html exposing (..)\nimport Html.Attributes exposing (..)\nimport Html.Attributes.Aria exposing (..)\nimport Html.Events exposing (..)\nimport Http\nimport Process\nimport Task as CoreTask\n\n\nmain =\n    Browser.document\n        { init = init\n        , view = view\n        , update = update\n        , subscriptions = subscriptions\n        }\n\n\ntype alias Model =\n    { positions : Array NamedPosition }\n\n\ninit : Int -> ( Model, Cmd Msg )\ninit x =\n    ( { positions = Array.repeat 2 Position.defaultNamedPosition }\n    , Map.create \"map\" MapOptions.default\n    )\n\n\nview : Model -> Browser.Document Msg\nview model =\n    let\n        hasAtLeastOnePositionName =\n            Array.toList model.positions\n                |> List.any (\\( positionName, _ ) -> not (String.isEmpty positionName))\n\n        expandedNav =\n            if hasAtLeastOnePositionName then\n                \"true\"\n\n            else\n                \"false\"\n    in\n    { title = \"Atlasr\"\n    , body =\n        [ main_ []\n            [ nav\n                [ ariaExpanded expandedNav ]\n                [ Html.form [ role \"search\", onSubmit Search ]\n                    [ input\n                        [ type_ \"text\"\n                        , onInput (NewPositionName 0)\n                        , ariaLabel \"Browse the world\"\n                        , ariaRequired True\n                        , placeholder \"Browse the world\"\n                        , value\n                            (Array.get 0 model.positions\n                                |> Maybe.map (\\( positionName, _ ) -> positionName)\n                                |> Maybe.withDefault Position.defaultName\n                            )\n                        ]\n                        []\n                    , input\n                        [ type_ \"text\"\n                        , onInput (NewPositionName 1)\n                        , ariaLabel \"Search for a direction\"\n                        , ariaRequired False\n                        , placeholder \"Search for a direction\"\n                        , value\n                            (Array.get 1 model.positions\n                                |> Maybe.map (\\( positionName, _ ) -> positionName)\n                                |> Maybe.withDefault Position.defaultName\n                            )\n                        ]\n                        []\n                    , input\n                        [ type_ \"submit\"\n                        , value \"Search\"\n                        ]\n                        []\n                    ]\n                , section [] []\n                ]\n            , article [ id \"map\" ] []\n            , footer [] [ text \"Atlasr\" ]\n            ]\n        ]\n    }\n\n\ntype Msg\n    = NewPositionName Int String\n    | GeoencodePositionNames (List NamedPosition)\n    | NewPositionGeocodes (Result Http.Error (List (Maybe Geocode.Geocode)))\n    | GetRoute (List Position)\n    | NewPositionRoute (Result Http.Error Route.Route)\n    | Search\n    | AddMarkers (List Position)\n    | RemoveMarkers\n    | ConnectMarkers (List Position)\n    | FlyTo Position\n    | Chain (List Msg)\n\n\nupdate : Msg -> Model -> ( Model, Cmd Msg )\nupdate msg model =\n    case msg of\n        NewPositionName index positionName ->\n            let\n                positions =\n                    Array.set index ( positionName, Position.defaultPosition ) model.positions\n            in\n            ( { model | positions = positions }, Cmd.none )\n\n        GeoencodePositionNames positionsToGeocode ->\n            ( model, Geocode.toGeocodes NewPositionGeocodes positionsToGeocode )\n\n        NewPositionGeocodes (Ok geocodes) ->\n            let\n                ( defaultLongitude, defaultLatitude ) =\n                    Position.defaultPosition\n\n                namedPositions =\n                    List.map\n                        (\\geocode_item ->\n                            Maybe.map\n                                (\\geocode ->\n                                    let\n                                        positionName =\n                                            geocode.label\n\n                                        position =\n                                            ( Maybe.withDefault defaultLongitude (String.toFloat geocode.longitude)\n                                            , Maybe.withDefault defaultLatitude (String.toFloat geocode.latitude)\n                                            )\n                                    in\n                                    ( positionName, position )\n                                )\n                                geocode_item\n                        )\n                        geocodes\n\n                positions =\n                    List.filterMap\n                        (\\namedPosition ->\n                            Maybe.map\n                                (\\( positionName, position ) -> Just position)\n                                namedPosition\n                                |> Maybe.withDefault Nothing\n                        )\n                        namedPositions\n            in\n            update\n                (Chain [ AddMarkers positions, GetRoute positions ])\n                { model\n                    | positions =\n                        List.map\n                            (\\namedPosition ->\n                                Maybe.withDefault Position.defaultNamedPosition namedPosition\n                            )\n                            namedPositions\n                            |> Array.fromList\n                }\n\n        NewPositionGeocodes (Err _) ->\n            ( model, Cmd.batch [] )\n\n        GetRoute positions ->\n            ( model, Route.toRoute NewPositionRoute positions )\n\n        NewPositionRoute (Ok route) ->\n            update\n                (ConnectMarkers route.points)\n                model\n\n        NewPositionRoute (Err _) ->\n            ( model, Cmd.batch [] )\n\n        Search ->\n            update\n                (Chain [ RemoveMarkers, Array.toList model.positions |> GeoencodePositionNames ])\n                model\n\n        AddMarkers positions ->\n            ( model, Map.addMarkers positions )\n\n        RemoveMarkers ->\n            ( model, Map.removeMarkers )\n\n        ConnectMarkers positions ->\n            ( model, Map.connectMarkers positions )\n\n        FlyTo position ->\n            ( model, Map.flyTo position )\n\n        Chain messages ->\n            let\n                chain message ( model1, commands ) =\n                    let\n                        ( nextModel, nextCommands ) =\n                            update message model\n                    in\n                    ( nextModel, Cmd.batch [ commands, nextCommands ] )\n            in\n            List.foldl chain ( model, Cmd.batch [] ) messages\n\n\nsubscriptions : Model -> Sub Msg\nsubscriptions model =\n    Sub.none\n"
  },
  {
    "path": "source/client/src/index.html",
    "content": "<!DOCTYPE html>\n\n<html>\n  <head>\n    <title>Atlasr</title>\n\n    <meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\" />\n    <meta http-equiv=\"content-type\" content=\"text/javascript; charset=utf-8\" />\n    <meta http-equiv=\"content-type\" content=\"text/css; charset=utf-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n\n    <link rel=\"preload\" href=\"/static/css/default.css\" as=\"style\" />\n    <link rel=\"preload\" href=\"/static/javascript/application.min.elm.js\" as=\"script\" />\n    <link rel=\"preload\" href=\"/static/javascript/main.js\" as=\"script\" />\n    <link rel=\"preload\" href=\"/static/javascript/mapbox-gl.min.js\" as=\"script\" />\n\n    <link rel=\"stylesheet\" href=\"/static/css/default.css\" media=\"all\" />\n    <script src=\"/static/javascript/application.min.elm.js\"></script>\n    <script src=\"/static/javascript/mapbox-gl.min.js\"></script>\n    <link rel=\"stylesheet\" href=\"/static/css/mapbox-gl.css\" media=\"all\" />\n  </head>\n  <body>\n    <script src=\"/static/javascript/main.js\" async defer></script>\n    <img id=\"body-background\" src=\"data:image/svg+xml,{MAP-PLACEHOLDER.svg}\" />\n  </body>\n</html>\n"
  },
  {
    "path": "source/client/tests/elm-package.json",
    "content": "{\n    \"version\": \"0.0.0\",\n    \"summary\": \"Atlasr's test suites\",\n    \"repository\": \"https://github.com/atlasr-org/atlasr.git\",\n    \"license\": \"BSD3\",\n    \"source-directories\": [\n        \"../src/\",\n        \"./unit/\",\n        \"./VerifyExamples/\"\n    ],\n    \"exposed-modules\": [],\n    \"dependencies\": {\n        \"eeue56/elm-html-test\": \"5.2.0 <= v < 6.0.0\",\n        \"elm-community/elm-test\": \"4.0.0 <= v < 5.0.0\",\n        \"elm-lang/core\": \"5.1.1 <= v < 6.0.0\",\n        \"elm-lang/html\": \"2.0.0 <= v < 3.0.0\",\n        \"elm-lang/http\": \"1.0.0 <= v < 2.0.0\",\n        \"fapian/elm-html-aria\": \"1.2.2 <= v < 2.0.0\"\n    },\n    \"elm-version\": \"0.18.0 <= v < 0.19.0\"\n}\n"
  },
  {
    "path": "source/client/tests/unit/Atlasr/Test/Position.elm",
    "content": "module Atlasr.Test.Position exposing (..)\n\nimport Atlasr.Position as Position\nimport Test exposing (..)\nimport Expect\n\n\nposition : Test\nposition =\n    describe \"Test suite for the `Position` module.\"\n        [ test \"Default position\" <|\n            \\() ->\n                Expect.equal ( 0.0, 0.0 ) Position.defaultPosition\n        , test \"Extract longitude\" <|\n            \\() ->\n                Expect.equal 1.2 (Position.extractLongitude ( 1.2, 3.4 ))\n        , test \"Extract latitude\" <|\n            \\() ->\n                Expect.equal 3.4 (Position.extractLatitude ( 1.2, 3.4 ))\n        , test \"Default name\" <|\n            \\() ->\n                Expect.equal \"\" Position.defaultName\n        , test \"Default named position\" <|\n            \\() ->\n                Expect.equal ( \"\", ( 0.0, 0.0 ) ) Position.defaultNamedPosition\n        ]\n"
  },
  {
    "path": "source/server/Cargo.toml",
    "content": "[package]\nname = \"atlasr-server\"\nversion = \"0.0.1\"\nauthors = [\"Ivan Enderlin <ivan.enderlin@hoa-project.net>\"]\n\n[dependencies]\nactix-web = \"^0.6.10\"\nfutures = \"^0.1.21\""
  },
  {
    "path": "source/server/src/main.rs",
    "content": "extern crate actix_web;\nextern crate futures;\n\nuse actix_web::{\n    App,\n    HttpRequest,\n    HttpResponse,\n    Result,\n    dev::HttpResponseBuilder,\n    fs::NamedFile,\n    http::Method,\n    client,\n    server,\n};\nuse futures::Future;\nuse std::path::PathBuf;\n\nmacro_rules! ROOT_DIRECTORY { () => { \"../../public/\" }; }\nconst STATIC_DIRECTORY: &'static str = concat!(ROOT_DIRECTORY!(), \"static/\");\nconst SERVER_ADDRESS: &'static str = env!(\"SERVER_ADDRESS\");\nconst ROUTE_API_ADDRESS: &'static str = env!(\"ROUTE_API_ADDRESS\");\nconst GEOCODE_API_ADDRESS: &'static str = env!(\"GEOCODE_API_ADDRESS\");\nconst TILE_API_ADDRESS: &'static str = env!(\"TILE_API_ADDRESS\");\n\nfn serve_static_files(request: HttpRequest) -> Result<NamedFile> {\n    let mut path: PathBuf = PathBuf::from(STATIC_DIRECTORY);\n    let tail: String = request.match_info().query(\"tail\")?;\n\n    path.push(tail);\n\n    Ok(NamedFile::open(path)?)\n}\n\nfn serve_api_geocode(request: HttpRequest) -> impl Future<Item=HttpResponse, Error=client::SendRequestError> {\n    client\n        ::get(format!(\"http://{}/search?{}\", GEOCODE_API_ADDRESS, request.query_string()))\n        .finish()\n        .unwrap()\n        .send()\n        .map(\n            |client_response| {\n                HttpResponseBuilder\n                    ::from(&client_response)\n                    .chunked()\n                    .streaming(client_response)\n            }\n        )\n}\n\nfn serve_api_route(request: HttpRequest) -> impl Future<Item=HttpResponse, Error=client::SendRequestError> {\n    client\n        ::get(format!(\"http://{}/route?{}\", ROUTE_API_ADDRESS, request.query_string()))\n        .finish()\n        .unwrap()\n        .send()\n        .map(\n            |client_response| {\n                HttpResponseBuilder\n                    ::from(&client_response)\n                    .chunked()\n                    .streaming(client_response)\n            }\n        )\n}\n\nfn serve_api_tile(request: HttpRequest) -> impl Future<Item=HttpResponse, Error=client::SendRequestError> {\n    let tail = request.match_info().get(\"tail\").unwrap_or(\"\");\n\n    client\n        ::get(format!(\"http://{}/{}\", TILE_API_ADDRESS, tail))\n        .finish()\n        .unwrap()\n        .send()\n        .map(\n            |client_response| {\n                HttpResponseBuilder\n                    ::from(&client_response)\n                    .chunked()\n                    .streaming(client_response)\n            }\n        )\n}\n\nfn serve_index(_request: HttpRequest) -> Result<NamedFile> {\n    let mut path: PathBuf = PathBuf::from(STATIC_DIRECTORY);\n\n    path.push(\"index.html\");\n\n    Ok(NamedFile::open(path)?)\n}\n\nfn main() {\n    server\n        ::new(\n            || {\n                vec![\n                    App::new()\n                        .prefix(\"/static\")\n                        .resource(\n                            r\"/{tail:.*}\",\n                            |resource| {\n                                resource.method(Method::GET).f(serve_static_files)\n                            }\n                        ),\n\n                    App::new()\n                        .prefix(\"/api\")\n                        .resource(\n                            \"/geocode\",\n                            |resource| {\n                                resource.method(Method::GET).a(serve_api_geocode)\n                            }\n                        )\n                        .resource(\n                            \"/route\",\n                            |resource| {\n                                resource.method(Method::GET).a(serve_api_route)\n                            }\n                        )\n                        .resource(\n                            \"/tile/{tail:.*}\",\n                            |resource| {\n                                resource.method(Method::GET).a(serve_api_tile)\n                            }\n                        ),\n\n                    App::new()\n                        .prefix(\"/\")\n                        .resource(\n                            \"/index.html\",\n                            |resource| {\n                                resource.method(Method::GET).f(serve_index)\n                            }\n                        )\n                        .resource(\n                            \"/\",\n                            |resource| {\n                                resource.method(Method::GET).f(serve_index)\n                            }\n                        )\n                ]\n            }\n        )\n        .bind(SERVER_ADDRESS)\n        .expect(&format!(\"Cannot bind the server to {}.\", SERVER_ADDRESS))\n        .shutdown_timeout(30)\n        .run();\n}\n"
  }
]