Repository: atlasr-org/atlasr
Branch: master
Commit: 6c36edf2367d
Files: 31
Total size: 63.8 KB
Directory structure:
gitextract_o8etyil8/
├── .gitignore
├── .gitmodules
├── LICENSE
├── README.md
├── justfile
├── public/
│ └── static/
│ ├── css/
│ │ └── default.css
│ ├── image/
│ │ └── README.md
│ └── javascript/
│ └── main.js
└── source/
├── api/
│ ├── geocode/
│ │ ├── indexer/
│ │ │ ├── Cargo.toml
│ │ │ └── src/
│ │ │ └── main.rs
│ │ └── searcher/
│ │ ├── Cargo.toml
│ │ └── src/
│ │ └── main.rs
│ └── tile/
│ ├── Cargo.toml
│ ├── database/
│ │ └── .keep
│ ├── diesel.toml
│ └── src/
│ ├── main.rs
│ ├── models.rs
│ └── schema.rs
├── client/
│ ├── elm.json
│ ├── src/
│ │ ├── Atlasr/
│ │ │ ├── Geocode.elm
│ │ │ ├── Map.elm
│ │ │ ├── MapboxGL/
│ │ │ │ ├── Options.elm
│ │ │ │ └── Ports.elm
│ │ │ ├── Position.elm
│ │ │ └── Route.elm
│ │ ├── Main.elm
│ │ └── index.html
│ └── tests/
│ ├── elm-package.json
│ └── unit/
│ └── Atlasr/
│ └── Test/
│ └── Position.elm
└── server/
├── Cargo.toml
└── src/
└── main.rs
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
/public/static/css/mapbox-gl.*
/public/static/index.html
/public/static/javascript/*.elm.js*
/public/static/javascript/mapbox-gl.*
/source/api/geocode/*.tsv
/source/api/geocode/index/
/source/api/geocode/indexer/target/
/source/api/geocode/searcher/target/
/source/api/tile/target/
/source/api/tile/database/*.mbtiles
/source/client/elm-stuff
/source/client/tests/elm-stuff
/source/server/target
.idea/
.DS_Store
================================================
FILE: .gitmodules
================================================
[submodule "source/api/graphhopper"]
path = source/api/route
url = https://github.com/atlasr-org/graphhopper
branch = master
[submodule "source/map-style"]
path = source/map-style
url = https://github.com/atlasr-org/osm-liberty
================================================
FILE: LICENSE
================================================
New BSD License
Copyright © 2018, Ivan Enderlin.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of Atlasr nor the names of its contributors may be
used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
================================================
FILE: README.md
================================================
# 🌍 Atlasr
Atlasr is a truly open-source and free map browser. The goal is threefold:
1. Learn about all the layers and components that make a map,
2. Provide a ready-to-use set of tools to build a 100% open-source,
free, and standalone map browser,
3. Provide an alternative service to the famous [Google Maps][GMaps]
or [Apple Maps][AMAps], with no ads, with respect to user privacy,
with 100% open-source and free data, and the top 10 features of
Google Maps.
A map architecture is composed of the following components:
* **Map data**, A giant database containing all information about
roads, trails, cafés, railway stations, buildings etc. The biggest
major project is [OpenStreetMap][OSM],
* **Tile server**, A program that, given a specific region, reads
the map database and compiles those information in a certain
format like [`.mbtiles`][MBtiles] for instance. Each region of a
planet is named a _tile_. A tile is defined by a longitude, a
latitude, and a zoom (an altitude scale),
* **Map renderer**, A program that, given a set of tiles,
renders/draws a map, so each roads, buildings etc. finally come
alive at this step. A map renderer requires at least the following
components:
* **Tile decoder** to decode the tiles received from the tile
server,
* **Styles** to know how to draw the data from the map
(e.g. “roads must be blue with a white strike”),
* **Fonts** to render texts,
* **Icons** to represent some places with an image (like
hospitals, police stations, parcs etc.).
* **Geocoding**, A program that can find the longitude and the
latitude of a labellised element on earth, like a postal address,
a building name, or a river name for examples,
* **Routing**, A program that is able to find a path/route between
one or many points (longitude + latitude) given some constraints
(like the vehicule type, the road preference etc.). A route
preferably comes with descriptions, like “Turn right in 100m”,
“Follow A10 for 7km” etc.
Obviously, each component comes with thousands of details and
constraints. The previous list is a high overview of how it works.
## Goals
The open-source map ecosystem is mature. Many projects already exist
to address one component of the map architecture. However, a
mainstream tool that combined all these projects, based on 100%
open-source and free data, is still missing. Atlasr aims to be the
response to this problem.
The quality must be comparable with Google Maps or Apple Maps:
* Smooth and fast experience,
* Reliable data,
* Beautiful design for the map and the UI.
## Roadmap
The main technologies are the following: [Rust] for the server, [Elm]
for the client, and [PostgreSQL]/[SQLite] for the databases.
The actual roadmap is the following:
* **Map data**, _all data comes from OpenStreetMap_,
* **Tile server** [`source/api/tile/`]:
* [x] Vector tiles are pre-computed by [OpenMapTiles][OMT], which
relies on OpenStreetMap.
* [x] Format is `.mbtiles`,
* [x] Solid and robust tile server.
* **Map renderer** [`source/map-style`]:
* [x] Use [Mapbox GL JS] to render the map,
* [x] Elm ports to Mapbox GL (JavaScript) for the Web UI,
* [x] Use [OSM Liberty] for the style, forked to remove the
dependencies to external services, and to use only “local”
data,
* [x] Icons/sprites,
* [ ] Fonts.
* **Geocoding** [`source/api/geocode`]:
* [x] Data are pre-computed by [OSM Names][OSMNames], which relies
on OpenStreetMap,
* [x] Transform the data to build an index for the search engine,
* [x] Custom and fast search engine to query the index, build with [Tantivy],
* [x] Solid and robust server.
* **Routing** [`source/api/route/`]:
* [x] Delegate all the works to [GraphHopper], only use the
open-source API. API to use:
* [x] Routing API,
* [ ] Isochrone API.
* **HTTP server** [`source/server/`]:
* [x] Fast and robust HTTP server between the client and all the API.
* **Client**/**Web UI** [`source/client`]:
* [x] Mobile-first,
* [x] Smooth and fast,
* [x] Search one or many positions (geocoding),
* [x] Search a route (routing),
* [ ] Search a route with constraints,
* [ ] Enhance data with Wikipedia (photos, descriptions, metadata etc.),
* [ ] All links are sharable,
* More features.
**Current focus**: The current hard work is to provide all map
components as local and standalone instances. Everything has been
addressed except the fonts in the map renderer (yet).
**Next focus**: Replace the top 10 features on Google Maps.
### Screenshots
* **Map renderer**: The tiles, the style, the icons, everything
comes from Atlasr. No external service is used.

* **Geocoding** and **Routing**: Atlasr is able to geoencode 2
postal addresses, and find a route between the two:

## Usages/Installations
[`just`] is required to run all the commands. Run `just --list` to get an overview of all the commands.
* **Tile server**:
```sh
$ # Install API tile server.
$ just install-api-tile
$ # Run the tile server.
$ just run-api-tile
```
* **Geocoding**:
```sh
$ # Download the data, install the indexer, and install the search engine.
$ just install-api-geocode
$ # Run the geocoding server.
$ just run-api-geocode-searcher
```
* **Routing**:
```sh
$ # Install the geocoding server.
$ just install-api-route
$ # Run the geocoding server.
$ just run-api-route
```
* **Client**/**Web UI**:
```sh
$ # Install the HTTP server for the client.
$ just install-server
$ # Run the HTTP server for the client.
$ just run-server
$ # Install the Web UI, its dependencies, and prepare the frontend.
$ just install-client
$ # Open the client.
$ just open
```
Enjoy!
## License
The entire project is under the BSD-3-Clause license. Please read the
`LICENSE` file.
[OSM]: https://openstreetmap.org/
[MBTiles]: https://github.com/mapbox/mbtiles-spec
[GMaps]: https://google.com/maps
[AMaps]: https://maps.apple.com/
[Rust]: https://rust-lang.org/
[Elm]: https://elm-lang.org/
[PostgreSQL]: https://www.postgresql.org/
[SQLite]: https://www.sqlite.org/
[OMT]: https://openmaptiles.org/
[Mapbox GL JS]: https://www.mapbox.com/mapbox-gl-js/api/
[OSM Liberty]: https://github.com/atlasr-org/osm-liberty/
[OSMNames]: http://osmnames.org/
[Tantivy]: https://github.com/tantivy-search/tantivy/
[GraphHopper]: https://www.graphhopper.com/
[`just`]: https://github.com/casey/just/
================================================
FILE: justfile
================================================
# vim: filetype=just
mapbox_gl_js_version = "0.50.0"
server_address = "localhost:8889"
geocode_api_address = "localhost:8990"
route_api_address = "localhost:8989"
geocode_data_planet = "https://github.com/OSMNames/OSMNames/releases/download/v2.0.4/planet-latest_geonames.tsv.gz"
tile_api_address = "127.0.0.1:8991"
# Open Atlasr in your favorite browser.
open: install
open http://{{server_address}}
# Install Atlasr!
install: install-server install-api install-client
# Test Atlasr.
test: test-server test-client
# Uninstall Atlasr.
uninstall: uninstall-server uninstall-api uninstall-client
# Install the HTTP server.
install-server:
cd source/server && \
SERVER_ADDRESS={{server_address}} \
ROUTE_API_ADDRESS={{route_api_address}} \
GEOCODE_API_ADDRESS={{geocode_api_address}} \
TILE_API_ADDRESS={{tile_api_address}} \
cargo build --release
# Test the HTTP server.
test-server:
cd source/server && cargo test
# Uninstall the HTTP server.
uninstall-server:
rm -rf source/server/Cargo.lock source/server/target
# Run the HTTP server (will not exit).
run-server: install-server
cd source/server && cargo run --release
# Install all the APIs.
install-api: install-api-geocode install-api-route install-api-tile
# Uninstall all the APIs.
uninstall-api: uninstall-api-geocode uninstall-api-route
# Install the geocode API.
install-api-geocode: install-api-geocode-data install-api-geocode-indexer install-api-geocode-searcher
# Install/download data for the geocode API.
install-api-geocode-data: install-api-geocode-indexer
cd source/api/geocode && \
curl -L {{geocode_data_planet}} > planet.tsv.gz && \
gzip -df planet.tsv.gz && \
mkdir -p index && \
./indexer/target/release/atlasr-api-geocode-indexer --source-file planet.tsv --index-directory index
# Install the indexer for the geocode API.
install-api-geocode-indexer:
cd source/api/geocode/indexer && cargo build --release
# Install the searcher for the geocode API.
install-api-geocode-searcher:
cd source/api/geocode/searcher && \
GEOCODE_API_ADDRESS={{geocode_api_address}} \
cargo build --release
uninstall-api-geocode:
# noop
run-api-geocode-searcher:
cd source/api/geocode/searcher && cargo run --release
# Install the route API (GraphHopper).
install-api-route:
git submodule update --init source/api/route
# Uninstall the route API (GraphHopper).
uninstall-api-route:
# noop
# Run the route API (GraphHopper) for a particular PBF zone.
run-api-route map_file='europe_switzerland': install-api-route
cd source/api/route && ./graphhopper.sh web {{map_file}}.pbf
# Install the tile API.
install-api-tile:
cd source/api/tile && \
SERVER_ADDRESS={{server_address}} \
TILE_API_ADDRESS={{tile_api_address}} \
cargo build --release
# Run the tile API.
run-api-tile:
cd source/api/tile && cargo run --release
# Uninstall the tile API.
uninstall-api-tile:
# noop
#run-api-tile map_file='europe_switzerland': install-api-tile
# cd source/api/tile && \
# curl -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
# Install client.
install-client: install-client-index install-client-application install-client-dependencies
# Test client.
test-client: test-client-application
# Uninstall client.
uninstall-client: uninstall-client-index uninstall-client-application
# Create a public `index.html` file.
SED_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\"`@\""
install-client-index:
#!/bin/sh
cp source/client/src/index.html public/static/index.html
if [[ "$OSTYPE" == "darwin"* ]]; then
sed -i '' {{SED_INPLACE_OPTION}} public/static/index.html
else
sed -i {{SED_INPLACE_OPTION}} public/static/index.html
fi
# Remove the public `index.html` file.
uninstall-client-index:
rm -f public/static/index.html
# Compile the Elm application to JS.
install-client-application:
cd source/client && elm make src/Main.elm --optimize --output ../../public/static/javascript/application.elm.js
cd public/static/javascript && \
uglifyjs application.elm.js \
--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' | \
uglifyjs \
--mangle \
--output=application.min.elm.js && \
gzip --best --stdout application.min.elm.js > application.min.elm.js.gz && \
brotli --best --stdout application.min.elm.js > application.min.elm.js.br
# Test the Elm application.
test-client-application:
cd source/client && elm-test tests/unit/
# Remove the Elm build artifact.
uninstall-client-application:
rm -f public/static/javascript/application.elm.js
# Install client dependencies.
install-client-dependencies: install-mapbox-gl-js install-mapbox-gl-css
# Uninstall client dependencies.
uninstall-client-dependencies: uninstall-mapbox-gl-js uninstall-mapbox-gl-css
# Install Mapbox GL JS, so JS and SourceMap.
install-mapbox-gl-js:
cd public/static/javascript/ && \
curl -L https://api.tiles.mapbox.com/mapbox-gl-js/v{{mapbox_gl_js_version}}/mapbox-gl.js > mapbox-gl.js && \
curl -L https://api.tiles.mapbox.com/mapbox-gl-js/v{{mapbox_gl_js_version}}/mapbox-gl.js.map > mapbox-gl.js.map && \
uglifyjs \
--compress \
--mangle \
--output=mapbox-gl.min.js mapbox-gl.js && \
gzip --best --stdout mapbox-gl.min.js > mapbox-gl.min.js.gz && \
brotli --best --stdout mapbox-gl.min.js > mapbox-gl.min.js.br
# Install Mapbox GL CSS
install-mapbox-gl-css:
cd public/static/css/ && \
curl -L https://api.tiles.mapbox.com/mapbox-gl-js/v{{mapbox_gl_js_version}}/mapbox-gl.css > mapbox-gl.css
# Uninstall Mapbox GL JS.
uninstall-mapbox-gl-js:
rm public/static/javascript/mapbox-gl.js
rm public/static/javascript/mapbox-gl.js.map
# Uninstall Mapbox GL CSS.
uninstall-mapbox-gl-css:
rm public/static/css/mapbox-gl.css
# Local Variables:
# mode: makefile
# End:
================================================
FILE: public/static/css/default.css
================================================
:root {
--brand-color : hsla(221, 40%, 14%, 1 );
--alt-color-lighter : hsla( 9, 93%, 100%, 1 );
--alt-color-light : hsla( 9, 50%, 72%, 1 );
--alt-color : hsla( 9, 93%, 72%, 1 );
--alt-color-dark : hsla( 9, 93%, 25%, .7);
--spacing : 1rem;
--spacing-small : calc(var(--spacing) / 2);
--spacing-large : calc(var(--spacing) * 2);
--border-radius : 2px;
--transition-duration: .1s;
--panel-width : 100vw;
--panel-height : 70vh;
--panel-min-width : auto;
--panel-max-width : auto;
--panel-unexpanded-translate-x : 0;
--panel-unexpanded-translate-y : -100%;
--panel-expanded-map-translate-x: 0;
--panel-expanded-map-translate-y: 35vh;
}
@media all and (min-width: 600px) {
:root {
--panel-width : 30vw;
--panel-height : 100vh;
--panel-min-width : 300px;
--panel-max-width : 500px;
--panel-unexpanded-translate-x : -100%;
--panel-unexpanded-translate-y : 0;
--panel-expanded-map-translate-x: 15vw;;
--panel-expanded-map-translate-y: 0;
}
}
@font-face {
font-family: Text;
src: url('../font/OpenSans-regular.woff') format('woff');
font-weight: normal;
font-style: normal;
font-display: swap;
}
* {
box-sizing: border-box !important;
margin: 0;
padding: 0;
}
[aria-hidden="true"] {
display: none;
}
html {
font-size: 100%;
}
body {
color: var(--brand-color);
font: 1.1rem/1.4rem Text, system-ui, sans-serif;
-webkit-font-smoothing: antialiased;
height: 100vh;
}
#body-background {
position: absolute;
object-fit: cover;
width: 100%;
height: 100%;
}
main {
position: relative;
width: 100vw;
height: 100vh;
overflow: hidden;
}
nav > form[role="search"] {
position: fixed;
top: var(--spacing);
padding: 0 var(--spacing);
width: var(--panel-width);
min-width: var(--panel-min-width);
max-width: var(--panel-max-width);
z-index: 2;
}
nav > section {
z-index: 1;
position: absolute;
width: var(--panel-width);
min-width: var(--panel-min-width);
max-width: var(--panel-max-width);
height: var(--panel-height);
padding: var(--spacing);
display: flex;
flex-direction: column;
background: var(--alt-color);
transform:
translateX(var(--panel-unexpanded-translate-x))
translateY(var(--panel-unexpanded-translate-y));
transition: var(--transition-duration) transform ease-in-out;
}
nav[aria-expanded="true"] > section {
box-shadow: 3px 0 var(--spacing) var(--alt-color-dark);
transform: translateX(0) translateY(0);
}
article {
position: absolute !important;
width: 100vw;
height: 100vh;
}
article > .mapboxgl-canvas-container {
transform: translateX(0) translateY(0);
transition: var(--transition-duration) transform ease-in-out;
}
nav[aria-expanded="true"] ~ article > .mapboxgl-canvas-container {
transform:
translateX(var(--panel-expanded-map-translate-x))
translateY(var(--panel-expanded-map-translate-y));
}
footer {
position: fixed;
bottom: var(--spacing);
right: var(--spacing);
color: var(--alt-color);
font-size: 1.1em;
font-weight: bold;
font-variant: all-small-caps;
letter-spacing: .1em;
text-shadow:
var(--alt-color-lighter) 0 2px,
var(--alt-color-lighter) 0 -2px,
var(--alt-color-lighter) 2px 0,
var(--alt-color-lighter) -2px 0;
}
@media all and (min-width: 600px) {
footer {
right: auto;
left: 50%;
transform: translateX(-50%);
}
}
input {
all: unset;
-moz-appearance: none;
-webkit-appearance: none;
appearance: none;
width: 100%;
padding: var(--spacing-small) var(--spacing);
line-height: var(--spacing-large);
border: 1px var(--alt-color-light) solid;
border-radius: var(--border-radius);
background: var(--alt-color-lighter);
}
#map {
font: inherit;
}
#map .mapboxgl-ctrl-group {
border: 1px var(--alt-color-light) solid;
box-shadow: unset;
}
#map .mapboxgl-ctrl-icon {
border-color: var(--alt-color-light);
}
#map .mapboxgl-ctrl-attrib {
font-size: .6em;
background: hsla(0, 0%, 100%, .8);
}
================================================
FILE: public/static/image/README.md
================================================
# `map-placeholder.svg`
1. Raster a part of the map,
2. Install [`primitive`](https://github.com/fogleman/primitive),
3. `primitive -m 1 -n 60 -i map-rasterized.png -o map-triangles.svg`,
4. Optimise it with [SVGOMG](https://jakearchibald.github.io/svgomg/),
5. Put the paths (only) into the following template:
```xml
<svg xmlns="http://www.w3.org/2000/svg" width="1024" height="540" version="1">
<filter id="blur">
<feGaussianBlur stdDeviation="12" />
</filter>
<g filter="url(#blur)">
…
</g>
</svg>
```
================================================
FILE: public/static/javascript/main.js
================================================
(
() => {
const atlasr = Elm.Atlasr.Main.init({
node: document.body,
flags: 1
});
const LAYER_ROUTE_MARKERS_PREFIX = 'markers-';
let map = null;
let layer_route_markers = '';
let markers = [];
atlasr.ports.mapboxgl_create_map.subscribe(
([id, options]) => {
mapboxgl.accessToken = 'undefined';
map = new mapboxgl.Map({
container : id,
style : options.style,
center : options.center,
zoom : options.zoom,
hash : options.hash,
pitchWithRotate : options.pitchWithRotate,
attributionControl: options.attributionControl,
dragRotate : options.dragRotate,
dragPan : options.dragPan,
keyboard : options.keyboard,
doubleClickZoom : options.doubleClickZoom,
touchZoomRotate : options.touchZoomRotate,
trackResize : options.trackResize,
renderWorldCopies : options.renderWorldCopies
});
map.addControl(
new mapboxgl.GeolocateControl(
{
positionOptions: {
enableHighAccuracy: true
},
trackUserLocation: true
}
),
'bottom-right'
);
map.addControl(
new mapboxgl.NavigationControl(),
'bottom-right'
);
}
);
atlasr.ports.mapboxgl_fly_to.subscribe(
(cameraOptions) => {
map.flyTo(cameraOptions);
}
);
atlasr.ports.mapboxgl_add_markers.subscribe(
(positions) => {
for (let [longitude, latitude] of positions) {
markers.push(
new mapboxgl.Marker()
.setLngLat([longitude, latitude])
.addTo(map)
);
}
}
);
atlasr.ports.mapboxgl_remove_markers.subscribe(
() => {
for (let marker of markers) {
marker.remove();
}
markers = [];
if (undefined !== map.getLayer(layer_route_markers)) {
map.removeLayer(layer_route_markers);
}
}
);
atlasr.ports.mapboxgl_connect_markers.subscribe(
(positions) => {
if (1 < positions.length) {
layer_route_markers = LAYER_ROUTE_MARKERS_PREFIX + guid();
map.addLayer({
'id': layer_route_markers,
'type': 'line',
'source': {
'type': 'geojson',
'data': {
'type': 'Feature',
'properties': {},
'geometry': {
'type': 'LineString',
'coordinates': positions
}
}
},
'layout': {
'line-join': 'round',
'line-cap': 'round'
},
'paint': {
'line-color': '#00b3fd',
'line-width': 8
}
});
}
}
);
function guid() {
const s4 = () => {
return Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1);
};
return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
}
}
)();
================================================
FILE: source/api/geocode/indexer/Cargo.toml
================================================
[package]
name = "atlasr-api-geocode-indexer"
version = "0.1.0"
authors = ["Ivan Enderlin <ivan.enderlin@hoa-project.net>"]
[dependencies]
tantivy = "^0.6"
clap = "^2.31.2"
failure = "^0.1.1"
csv = "^1.0.0"
serde = "^1.0.66"
serde_derive = "^1.0.66"
================================================
FILE: source/api/geocode/indexer/src/main.rs
================================================
extern crate failure;
extern crate clap;
extern crate csv;
#[macro_use] extern crate serde_derive;
extern crate tantivy;
use failure::{
Error,
ResultExt
};
use clap::{App, Arg};
use tantivy::{
Index,
IndexWriter,
directory,
schema::*
};
use std::fs::File;
use std::io::{
self,
BufReader,
Write
};
/// [Definition of these fields](http://osmnames.readthedocs.io/en/latest/introduction.html#output-format).
#[derive(Debug, Deserialize)]
struct Record {
name: String,
alternative_names: Option<String>,
osm_type: String,
osm_id: u64,
class: String,
#[serde(rename = "type")]
object_type: String,
#[serde(rename = "lon")]
longitude: Option<f64>,
#[serde(rename = "lat")]
latitude: Option<f64>,
place_rank: Option<u64>,
importance: Option<f64>,
street: Option<String>,
city: Option<String>,
county: Option<String>,
state: Option<String>,
country: Option<String>,
country_code: Option<String>,
display_name: String,
#[serde(rename = "west")]
bbox_west: Option<f64>,
#[serde(rename = "south")]
bbox_south: Option<f64>,
#[serde(rename = "east")]
bbox_east: Option<f64>,
#[serde(rename = "north")]
bbox_north: Option<f64>,
wikidata: Option<String>,
wikipedia: Option<String>,
housenumbers: Option<String>
}
impl Record {
fn indexable(&self) -> bool {
if self.longitude.is_none() || self.latitude.is_none() {
return false;
}
match (self.class.as_str(), self.object_type.as_str()) {
("boundary", _) |
("highway", "residential") => {
return true;
},
(_, _) => { }
}
false
}
}
fn create_schema() -> Schema {
let mut schema_builder = SchemaBuilder::default();
schema_builder.add_text_field("display_name", TEXT | STORED);
schema_builder.add_text_field("class", STORED);
schema_builder.add_text_field("type", STORED);
schema_builder.add_text_field("longitude", STORED);
schema_builder.add_text_field("latitude", STORED);
schema_builder.build()
}
fn create_index(index_directory: directory::MmapDirectory, schema: &Schema) -> Index {
Index::create(index_directory, schema.clone()).unwrap()
}
fn index_record(index_writer: &mut IndexWriter, schema: &Schema, record: Record) -> tantivy::Result<()> {
if ! record.indexable() {
return Ok(());
}
let field_display_name = schema.get_field("display_name").unwrap();
let field_class = schema.get_field("class").unwrap();
let field_type = schema.get_field("type").unwrap();
let field_longitude = schema.get_field("longitude").unwrap();
let field_latitude = schema.get_field("latitude").unwrap();
let mut document = Document::default();
document.add_text(field_display_name, &record.display_name);
document.add_text(field_class, &record.class);
document.add_text(field_type, &record.object_type);
document.add_text(field_longitude, &record.longitude.unwrap().to_string());
document.add_text(field_latitude, &record.latitude.unwrap().to_string());
index_writer.add_document(document);
Ok(())
}
fn main() -> Result<(), Error> {
let matches =
App::new("atlasr-api-geocode-indexer")
.version(env!("CARGO_PKG_VERSION"))
.about("Atlasr: Indexer for the geocode API.")
.author("Ivan Enderlin")
.arg(
Arg::with_name("source-file")
.help("File containing the data to index, must be a `.tsv` file.")
.short("s")
.long("source-file")
.value_name("SOURCE_FILE")
.takes_value(true)
.required(true)
)
.arg(
Arg::with_name("index-directory")
.help("Directory that will contain the index.")
.short("i")
.long("index-directory")
.value_name("INDEX_DIRECTORY")
.takes_value(true)
.required(true)
)
.get_matches();
let source_file_name = matches.value_of("source-file").unwrap();
let source_file = File::open(source_file_name).context("Cannot open the given file.")?;
let source_buffer = BufReader::new(source_file);
let mut source_reader =
csv::ReaderBuilder::new()
.delimiter(b'\t')
.double_quote(false)
.flexible(true)
.from_reader(source_buffer);
let index_directory = matches.value_of("index-directory").unwrap();
let schema = create_schema();
let index = create_index(directory::MmapDirectory::open(index_directory)?, &schema);
let mut index_writer = index.writer(50_000_000)?;
let stdout = io::stdout();
let mut handle = stdout.lock();
for record in source_reader.deserialize() {
let record: Record = record?;
handle.write(b"~~> ").unwrap();
handle.write(record.name.as_bytes()).unwrap();
handle.write(b"\n").unwrap();
index_record(&mut index_writer, &schema, record)?;
}
index_writer.commit()?;
Ok(())
}
================================================
FILE: source/api/geocode/searcher/Cargo.toml
================================================
[package]
name = "atlasr-api-geocode-searcher"
version = "0.1.0"
authors = ["Ivan Enderlin <ivan.enderlin@hoa-project.net>"]
[dependencies]
tantivy = "^0.6"
actix-web = "^0.6.10"
================================================
FILE: source/api/geocode/searcher/src/main.rs
================================================
extern crate actix_web;
extern crate tantivy;
use actix_web::{
App,
HttpRequest,
HttpResponse,
http::Method,
server
};
use tantivy::{
Index,
collector::TopCollector,
directory,
query::QueryParser
};
use std::cmp::min;
const GEOCODE_API_ADDRESS: &'static str = env!("GEOCODE_API_ADDRESS");
struct SearchState {
index: Index,
query_parser: QueryParser
}
fn serve_search(request: HttpRequest<SearchState>) -> HttpResponse {
let term: &str;
match request.query().get("term") {
Some(value) => term = value,
None => return HttpResponse::BadRequest().reason("Query `term` is missing").finish()
}
let limit: u8 = match request.query().get("limit") {
Some(value) => min(value.parse().unwrap_or(3), 10),
None => 3
};
let index = &request.state().index;
let query_parser = &request.state().query_parser;
let schema = index.schema();
let searcher = index.searcher();
let query = match query_parser.parse_query(term) {
Ok(value) => {
value
},
Err(_) => {
return HttpResponse::InternalServerError().finish();
}
};
let mut top_collector = TopCollector::with_limit(limit as usize);
match searcher.search(&*query, &mut top_collector) {
Err(_) => {
return HttpResponse::InternalServerError().finish();
}
_ => { }
}
let mut response = String::from("[");
for document in top_collector.docs() {
let retrieved_document = match searcher.doc(&document) {
Ok(value) => {
value
},
Err(_) => {
return HttpResponse::InternalServerError().finish();
}
};
response.push_str(&schema.to_json(&retrieved_document));
response.push_str(",");
}
if response.len() > 1 {
response.pop();
}
response.push_str("]");
HttpResponse::Ok().content_type("application/json").body(response)
}
fn main() {
server
::new(
|| {
App
::with_state(
{
let index = Index::open(directory::MmapDirectory::open("../index").unwrap()).unwrap();
index.load_searchers().unwrap();
let schema = index.schema();
let query_parser = QueryParser::for_index(
&index,
vec![
schema.get_field("display_name").unwrap()
]
);
SearchState {
index: index,
query_parser: query_parser
}
}
)
.resource(
"/search", // ?term=…&limit=…
|resource| {
resource.method(Method::GET).f(serve_search)
}
)
}
)
.bind(GEOCODE_API_ADDRESS)
.expect(&format!("Cannot bind the server to {}.", GEOCODE_API_ADDRESS))
.shutdown_timeout(30)
.run();
}
================================================
FILE: source/api/tile/Cargo.toml
================================================
[package]
name = "atlasr-api-tile"
version = "0.1.0"
authors = ["Ivan Enderlin <ivan.enderlin@hoa-project.net>"]
[dependencies]
warp = "^0.1.9"
diesel = { version = "^1.3.3", features = ["sqlite"] }
r2d2 = "^0.8.3"
r2d2-diesel = "^1.0.0"
serde_json = "^1.0.0"
================================================
FILE: source/api/tile/database/.keep
================================================
================================================
FILE: source/api/tile/diesel.toml
================================================
[print_schema]
file = "src/schema.rs"
================================================
FILE: source/api/tile/src/main.rs
================================================
#[macro_use]
extern crate warp;
#[macro_use]
extern crate diesel;
extern crate r2d2;
extern crate r2d2_diesel;
extern crate serde_json as json;
use warp::Filter;
use warp::http;
use diesel::prelude::*;
use r2d2_diesel::ConnectionManager;
use std::net::SocketAddr;
use std::str::FromStr;
mod schema;
mod models;
const SERVER_ADDRESS: &'static str = env!("SERVER_ADDRESS");
const TILE_API_ADDRESS: &'static str = env!("TILE_API_ADDRESS");
fn not_found() -> http::Result<http::Response<Vec<u8>>> {
http::Response::builder()
.status(http::StatusCode::NOT_FOUND)
.header("content-type", "text/plain; charset=utf-8")
.body(vec![])
}
fn error() -> http::Result<http::Response<String>> {
http::Response::builder()
.status(http::StatusCode::NOT_FOUND)
.header("content-type", "text/plain; charset=utf-8")
.body("error".to_string())
}
fn main() {
let manager = ConnectionManager::<SqliteConnection>::new("database/europe_switzerland.mbtiles");
let pool =
r2d2::Pool::builder()
.max_size(15)
.build(manager)
.expect("Failed to create pool.");
let pool1 = pool.clone();
let pool2 = pool.clone();
// `GET /<zoom_level>/<tile_column>/<tile_row>/`
let tiles =
path!(u8 / u16 / u16)
.map(
move |z, x, y| {
match pool1.get() {
Ok(connection) => {
use schema::tiles::dsl::*;
use models::Tiles;
let x = x as u32;
let y = y as u32;
let z = z as u32;
let y = 2u32.pow(z) - 1 - y;
let tile =
tiles
.filter(zoom_level.eq(z as i32))
.filter(tile_column.eq(x as i32))
.filter(tile_row.eq(y as i32))
.first::<Tiles>(&*connection);
match tile {
Ok(tile) => {
http::Response::builder()
.header("content-type", "application/x-protobuf")
.header("content-encoding", "gzip")
.body(tile.tile_data)
},
Err(_) => {
not_found()
}
}
},
Err(_) => {
not_found()
}
}
}
);
// `GET /metadata.json`
let metadata =
path!("metadata.json")
.map(
move || {
match pool2.get() {
Ok(connection) => {
use schema::metadata::dsl::*;
use models::Metadata;
match metadata.load::<Metadata>(&*connection) {
Ok(all_metadata) => {
let mut output = json::map::Map::with_capacity(all_metadata.len());
for metadatum in all_metadata {
let v = metadatum.value;
let map_value = match metadatum.name.as_ref() {
"bounds" | "center" => {
Ok(
json::Value::Array(
v
.splitn(4, ',')
.map(
|p| {
json::Value::Number(
json::Number::from_str(p).unwrap_or_else(|_| json::Number::from_f64(0.0).unwrap())
)
}
)
.collect()
)
)
},
"json" => {
if let Ok(json::Value::Object(meta_metadata)) = json::from_str(&v) {
output.extend(meta_metadata);
}
Err(())
},
_ => Ok(json::Value::String(v))
};
if let Ok(map_value) = map_value {
output.insert(metadatum.name, map_value);
}
}
if !output.contains_key("profile") {
output.insert("profile".to_string(), json::Value::String("mercator".to_string()));
}
if !output.contains_key("scale") {
output.insert("scale".to_string(), json::Value::Number(json::Number::from_f64(1.0).unwrap()));
}
if !output.contains_key("tilejson") {
output.insert("tilejson".to_string(), json::Value::String("2.0.0".to_string()));
}
if !output.contains_key("tiles") {
output.insert(
"tiles".to_string(),
json::Value::Array(vec![
json::Value::String(
format!("http://{}/api/tile/{{z}}/{{x}}/{{y}}", SERVER_ADDRESS)
)
])
);
}
http::Response::builder()
.header("content-type", "application/json")
.body(json::to_string(&output).unwrap())
},
Err(_) => error()
}
},
Err(_) => error()
}
}
);
let routes = tiles.or(metadata);
warp::serve(routes)
.run(TILE_API_ADDRESS.parse::<SocketAddr>().expect(&format!("Cannot bind the server to {}", TILE_API_ADDRESS)));
}
================================================
FILE: source/api/tile/src/models.rs
================================================
#[derive(Debug, Queryable)]
pub struct Tiles {
pub zoom_level: i32,
pub tile_column: i32,
pub tile_row: i32,
pub tile_data: Vec<u8>,
}
#[derive(Debug, Queryable)]
pub struct Metadata {
pub name: String,
pub value: String
}
================================================
FILE: source/api/tile/src/schema.rs
================================================
table! {
tiles (zoom_level, tile_column, tile_row) {
zoom_level -> Integer,
tile_column -> Integer,
tile_row -> Integer,
tile_data -> Binary,
}
}
table! {
metadata (name, value) {
name -> Text,
value -> Text,
}
}
================================================
FILE: source/client/elm.json
================================================
{
"type": "application",
"source-directories": [
"src"
],
"elm-version": "0.19.0",
"dependencies": {
"direct": {
"elm/browser": "1.0.1",
"elm/core": "1.0.0",
"elm/html": "1.0.0",
"elm/http": "1.0.0",
"elm/json": "1.0.0",
"elm/url": "1.0.0",
"fapian/elm-html-aria": "1.4.0"
},
"indirect": {
"elm/time": "1.0.0",
"elm/virtual-dom": "1.0.2"
}
},
"test-dependencies": {
"direct": {},
"indirect": {}
}
}
================================================
FILE: source/client/src/Atlasr/Geocode.elm
================================================
module Atlasr.Geocode exposing (Geocode, toGeocodes)
import Atlasr.Position exposing (NamedPosition, defaultNamedPosition)
import Http
import Json.Decode
import Task
type alias Geocode =
{ label : String
, longitude : String
, latitude : String
}
{-| Geocode a list of position names.
-}
toGeocodes : (Result Http.Error (List (Maybe Geocode)) -> msg) -> List NamedPosition -> Cmd msg
toGeocodes outputType positionsToGeocode =
let
( defaultName, defaultPosition ) =
defaultNamedPosition
tasks =
List.map
(\( positionName, position ) ->
if positionName /= defaultName then
if position == defaultPosition then
positionToGeocodeRequest positionName
|> Http.toTask
|> Task.map (\geocode -> Just geocode)
else
let
( longitude, latitude ) =
position
geocode =
{ label = positionName
, longitude = String.fromFloat longitude
, latitude = String.fromFloat latitude
}
in
Task.succeed (Just geocode)
else
Task.succeed Nothing
)
positionsToGeocode
in
Task.attempt outputType <| Task.sequence tasks
{-| Create an HTTP request to geocode a position.
-}
positionToGeocodeRequest : String -> Http.Request Geocode
positionToGeocodeRequest positionName =
let
url =
"/api/geocode?term=" ++ positionName ++ "&limit=1"
in
Http.get url decodeGeocode
{-| Decoder for the geocode payload from the HTTP service.
-}
decodeGeocode : Json.Decode.Decoder Geocode
decodeGeocode =
Json.Decode.at [ "0" ]
(Json.Decode.map3
Geocode
(Json.Decode.at [ "display_name", "0" ] Json.Decode.string)
(Json.Decode.at [ "longitude", "0" ] Json.Decode.string)
(Json.Decode.at [ "latitude", "0" ] Json.Decode.string)
)
================================================
FILE: source/client/src/Atlasr/Map.elm
================================================
module Atlasr.Map exposing (addMarkers, connectMarkers, create, flyTo, removeMarkers)
import Atlasr.MapboxGL.Options as Options
import Atlasr.MapboxGL.Ports exposing (..)
import Atlasr.Position exposing (Position)
{-| Create a map with an ID and some options.
-}
create : String -> Options.Map -> Cmd msg
create id options =
mapboxgl_create_map ( id, options )
{-| Move the camera to a specific position.
-}
flyTo : Position -> Cmd msg
flyTo position =
mapboxgl_fly_to { center = position }
{-| Add markers to certain positions, and connect them.
-}
addMarkers : List Position -> Cmd msg
addMarkers positions =
mapboxgl_add_markers positions
{-| Remove all markers.
-}
removeMarkers : Cmd msg
removeMarkers =
mapboxgl_remove_markers ()
{-| Draw lines between points/positions.
-}
connectMarkers : List Position -> Cmd msg
connectMarkers positions =
mapboxgl_connect_markers positions
================================================
FILE: source/client/src/Atlasr/MapboxGL/Options.elm
================================================
module Atlasr.MapboxGL.Options exposing (Camera, Map, default)
import Atlasr.Position exposing (Position)
type alias Map =
{ style : String
, center : Position
, zoom : Int
, hash : Bool
, pitchWithRotate : Bool
, attributionControl : Bool
, dragRotate : Bool
, dragPan : Bool
, keyboard : Bool
, doubleClickZoom : Bool
, touchZoomRotate : Bool
, trackResize : Bool
, renderWorldCopies : Bool
}
default : Map
default =
{ style = "/static/map-style/style.json"
, center = ( 2.294504285127, 48.858262790681 )
, zoom = 15
, hash = True
, pitchWithRotate = True
, attributionControl = True
, dragRotate = True
, dragPan = True
, keyboard = True
, doubleClickZoom = True
, touchZoomRotate = True
, trackResize = True
, renderWorldCopies = True
}
type alias Camera =
{ center : Position }
================================================
FILE: source/client/src/Atlasr/MapboxGL/Ports.elm
================================================
port module Atlasr.MapboxGL.Ports exposing (mapboxgl_add_markers, mapboxgl_connect_markers, mapboxgl_create_map, mapboxgl_fly_to, mapboxgl_remove_markers)
import Atlasr.MapboxGL.Options as Options
import Atlasr.Position exposing (Position)
port mapboxgl_create_map : ( String, Options.Map ) -> Cmd msg
port mapboxgl_fly_to : Options.Camera -> Cmd msg
port mapboxgl_add_markers : List Position -> Cmd msg
port mapboxgl_remove_markers : () -> Cmd msg
port mapboxgl_connect_markers : List Position -> Cmd msg
================================================
FILE: source/client/src/Atlasr/Position.elm
================================================
module Atlasr.Position exposing (Latitude, Longitude, NamedPosition, Position, defaultName, defaultNamedPosition, defaultPosition, extractLatitude, extractLongitude)
type alias Longitude =
Float
type alias Latitude =
Float
type alias Position =
( Longitude, Latitude )
{-| Allocate a default position.
-}
defaultPosition : Position
defaultPosition =
( 0.0, 0.0 )
{-| Extract the longitude of a position.
-}
extractLongitude : Position -> Float
extractLongitude ( longitude, _ ) =
longitude
{-| Extract the latitude of a position.
-}
extractLatitude : Position -> Float
extractLatitude ( _, latitude ) =
latitude
type alias NamedPosition =
( String, Position )
{-| Allocate a default position name.
-}
defaultName : String
defaultName =
""
{-| Allocate a default named position.
-}
defaultNamedPosition : NamedPosition
defaultNamedPosition =
( defaultName, defaultPosition )
================================================
FILE: source/client/src/Atlasr/Route.elm
================================================
module Atlasr.Route exposing (Route, toRoute)
import Atlasr.Position exposing (Position)
import Http
import Json.Decode
import Task
import Url.Builder exposing (absolute, string)
type alias Route =
{ points : List Position
}
type alias RawRoute =
{ points : List Point
}
type alias Point =
{ longitude : Float
, latitude : Float
}
emptyRoute : Route
emptyRoute =
{ points = [] }
{-| Get a route from a list of position.
-}
toRoute : (Result Http.Error Route -> msg) -> List Position -> Cmd msg
toRoute outputType positions =
let
task =
if List.length positions <= 1 then
Task.succeed emptyRoute
else
positionsToRouteRequest positions
|> Http.toTask
|> Task.map
(\route ->
Route
(List.map
(\point ->
( point.longitude, point.latitude )
)
route.points
)
)
in
Task.attempt outputType task
{-| Create an HTTP request to get the route between positions.
-}
positionsToRouteRequest : List Position -> Http.Request RawRoute
positionsToRouteRequest positions =
let
url =
absolute
[ "api/route" ]
([ string "points_encoded" "false"
, string "vehicle" "car"
]
++ List.map
(\position ->
let
( longitude, latitude ) =
position
in
string "point" (String.fromFloat latitude ++ "," ++ String.fromFloat longitude)
)
positions
)
in
Http.get url decodeRoute
{-| Decoder for the route payload from the HTTP service.
-}
decodeRoute : Json.Decode.Decoder RawRoute
decodeRoute =
Json.Decode.at [ "paths", "0" ]
(Json.Decode.map
RawRoute
(Json.Decode.at [ "points", "coordinates" ]
(Json.Decode.list
(Json.Decode.map2
Point
(Json.Decode.field "0" Json.Decode.float)
(Json.Decode.field "1" Json.Decode.float)
)
)
)
)
================================================
FILE: source/client/src/Main.elm
================================================
module Atlasr.Main exposing (Model, Msg(..), init, main, subscriptions, update, view)
import Array exposing (Array)
import Atlasr.Geocode as Geocode
import Atlasr.Map as Map
import Atlasr.MapboxGL.Options as MapOptions
import Atlasr.Position as Position exposing (NamedPosition, Position)
import Atlasr.Route as Route
import Browser
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Attributes.Aria exposing (..)
import Html.Events exposing (..)
import Http
import Process
import Task as CoreTask
main =
Browser.document
{ init = init
, view = view
, update = update
, subscriptions = subscriptions
}
type alias Model =
{ positions : Array NamedPosition }
init : Int -> ( Model, Cmd Msg )
init x =
( { positions = Array.repeat 2 Position.defaultNamedPosition }
, Map.create "map" MapOptions.default
)
view : Model -> Browser.Document Msg
view model =
let
hasAtLeastOnePositionName =
Array.toList model.positions
|> List.any (\( positionName, _ ) -> not (String.isEmpty positionName))
expandedNav =
if hasAtLeastOnePositionName then
"true"
else
"false"
in
{ title = "Atlasr"
, body =
[ main_ []
[ nav
[ ariaExpanded expandedNav ]
[ Html.form [ role "search", onSubmit Search ]
[ input
[ type_ "text"
, onInput (NewPositionName 0)
, ariaLabel "Browse the world"
, ariaRequired True
, placeholder "Browse the world"
, value
(Array.get 0 model.positions
|> Maybe.map (\( positionName, _ ) -> positionName)
|> Maybe.withDefault Position.defaultName
)
]
[]
, input
[ type_ "text"
, onInput (NewPositionName 1)
, ariaLabel "Search for a direction"
, ariaRequired False
, placeholder "Search for a direction"
, value
(Array.get 1 model.positions
|> Maybe.map (\( positionName, _ ) -> positionName)
|> Maybe.withDefault Position.defaultName
)
]
[]
, input
[ type_ "submit"
, value "Search"
]
[]
]
, section [] []
]
, article [ id "map" ] []
, footer [] [ text "Atlasr" ]
]
]
}
type Msg
= NewPositionName Int String
| GeoencodePositionNames (List NamedPosition)
| NewPositionGeocodes (Result Http.Error (List (Maybe Geocode.Geocode)))
| GetRoute (List Position)
| NewPositionRoute (Result Http.Error Route.Route)
| Search
| AddMarkers (List Position)
| RemoveMarkers
| ConnectMarkers (List Position)
| FlyTo Position
| Chain (List Msg)
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
NewPositionName index positionName ->
let
positions =
Array.set index ( positionName, Position.defaultPosition ) model.positions
in
( { model | positions = positions }, Cmd.none )
GeoencodePositionNames positionsToGeocode ->
( model, Geocode.toGeocodes NewPositionGeocodes positionsToGeocode )
NewPositionGeocodes (Ok geocodes) ->
let
( defaultLongitude, defaultLatitude ) =
Position.defaultPosition
namedPositions =
List.map
(\geocode_item ->
Maybe.map
(\geocode ->
let
positionName =
geocode.label
position =
( Maybe.withDefault defaultLongitude (String.toFloat geocode.longitude)
, Maybe.withDefault defaultLatitude (String.toFloat geocode.latitude)
)
in
( positionName, position )
)
geocode_item
)
geocodes
positions =
List.filterMap
(\namedPosition ->
Maybe.map
(\( positionName, position ) -> Just position)
namedPosition
|> Maybe.withDefault Nothing
)
namedPositions
in
update
(Chain [ AddMarkers positions, GetRoute positions ])
{ model
| positions =
List.map
(\namedPosition ->
Maybe.withDefault Position.defaultNamedPosition namedPosition
)
namedPositions
|> Array.fromList
}
NewPositionGeocodes (Err _) ->
( model, Cmd.batch [] )
GetRoute positions ->
( model, Route.toRoute NewPositionRoute positions )
NewPositionRoute (Ok route) ->
update
(ConnectMarkers route.points)
model
NewPositionRoute (Err _) ->
( model, Cmd.batch [] )
Search ->
update
(Chain [ RemoveMarkers, Array.toList model.positions |> GeoencodePositionNames ])
model
AddMarkers positions ->
( model, Map.addMarkers positions )
RemoveMarkers ->
( model, Map.removeMarkers )
ConnectMarkers positions ->
( model, Map.connectMarkers positions )
FlyTo position ->
( model, Map.flyTo position )
Chain messages ->
let
chain message ( model1, commands ) =
let
( nextModel, nextCommands ) =
update message model
in
( nextModel, Cmd.batch [ commands, nextCommands ] )
in
List.foldl chain ( model, Cmd.batch [] ) messages
subscriptions : Model -> Sub Msg
subscriptions model =
Sub.none
================================================
FILE: source/client/src/index.html
================================================
<!DOCTYPE html>
<html>
<head>
<title>Atlasr</title>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<meta http-equiv="content-type" content="text/javascript; charset=utf-8" />
<meta http-equiv="content-type" content="text/css; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="preload" href="/static/css/default.css" as="style" />
<link rel="preload" href="/static/javascript/application.min.elm.js" as="script" />
<link rel="preload" href="/static/javascript/main.js" as="script" />
<link rel="preload" href="/static/javascript/mapbox-gl.min.js" as="script" />
<link rel="stylesheet" href="/static/css/default.css" media="all" />
<script src="/static/javascript/application.min.elm.js"></script>
<script src="/static/javascript/mapbox-gl.min.js"></script>
<link rel="stylesheet" href="/static/css/mapbox-gl.css" media="all" />
</head>
<body>
<script src="/static/javascript/main.js" async defer></script>
<img id="body-background" src="data:image/svg+xml,{MAP-PLACEHOLDER.svg}" />
</body>
</html>
================================================
FILE: source/client/tests/elm-package.json
================================================
{
"version": "0.0.0",
"summary": "Atlasr's test suites",
"repository": "https://github.com/atlasr-org/atlasr.git",
"license": "BSD3",
"source-directories": [
"../src/",
"./unit/",
"./VerifyExamples/"
],
"exposed-modules": [],
"dependencies": {
"eeue56/elm-html-test": "5.2.0 <= v < 6.0.0",
"elm-community/elm-test": "4.0.0 <= v < 5.0.0",
"elm-lang/core": "5.1.1 <= v < 6.0.0",
"elm-lang/html": "2.0.0 <= v < 3.0.0",
"elm-lang/http": "1.0.0 <= v < 2.0.0",
"fapian/elm-html-aria": "1.2.2 <= v < 2.0.0"
},
"elm-version": "0.18.0 <= v < 0.19.0"
}
================================================
FILE: source/client/tests/unit/Atlasr/Test/Position.elm
================================================
module Atlasr.Test.Position exposing (..)
import Atlasr.Position as Position
import Test exposing (..)
import Expect
position : Test
position =
describe "Test suite for the `Position` module."
[ test "Default position" <|
\() ->
Expect.equal ( 0.0, 0.0 ) Position.defaultPosition
, test "Extract longitude" <|
\() ->
Expect.equal 1.2 (Position.extractLongitude ( 1.2, 3.4 ))
, test "Extract latitude" <|
\() ->
Expect.equal 3.4 (Position.extractLatitude ( 1.2, 3.4 ))
, test "Default name" <|
\() ->
Expect.equal "" Position.defaultName
, test "Default named position" <|
\() ->
Expect.equal ( "", ( 0.0, 0.0 ) ) Position.defaultNamedPosition
]
================================================
FILE: source/server/Cargo.toml
================================================
[package]
name = "atlasr-server"
version = "0.0.1"
authors = ["Ivan Enderlin <ivan.enderlin@hoa-project.net>"]
[dependencies]
actix-web = "^0.6.10"
futures = "^0.1.21"
================================================
FILE: source/server/src/main.rs
================================================
extern crate actix_web;
extern crate futures;
use actix_web::{
App,
HttpRequest,
HttpResponse,
Result,
dev::HttpResponseBuilder,
fs::NamedFile,
http::Method,
client,
server,
};
use futures::Future;
use std::path::PathBuf;
macro_rules! ROOT_DIRECTORY { () => { "../../public/" }; }
const STATIC_DIRECTORY: &'static str = concat!(ROOT_DIRECTORY!(), "static/");
const SERVER_ADDRESS: &'static str = env!("SERVER_ADDRESS");
const ROUTE_API_ADDRESS: &'static str = env!("ROUTE_API_ADDRESS");
const GEOCODE_API_ADDRESS: &'static str = env!("GEOCODE_API_ADDRESS");
const TILE_API_ADDRESS: &'static str = env!("TILE_API_ADDRESS");
fn serve_static_files(request: HttpRequest) -> Result<NamedFile> {
let mut path: PathBuf = PathBuf::from(STATIC_DIRECTORY);
let tail: String = request.match_info().query("tail")?;
path.push(tail);
Ok(NamedFile::open(path)?)
}
fn serve_api_geocode(request: HttpRequest) -> impl Future<Item=HttpResponse, Error=client::SendRequestError> {
client
::get(format!("http://{}/search?{}", GEOCODE_API_ADDRESS, request.query_string()))
.finish()
.unwrap()
.send()
.map(
|client_response| {
HttpResponseBuilder
::from(&client_response)
.chunked()
.streaming(client_response)
}
)
}
fn serve_api_route(request: HttpRequest) -> impl Future<Item=HttpResponse, Error=client::SendRequestError> {
client
::get(format!("http://{}/route?{}", ROUTE_API_ADDRESS, request.query_string()))
.finish()
.unwrap()
.send()
.map(
|client_response| {
HttpResponseBuilder
::from(&client_response)
.chunked()
.streaming(client_response)
}
)
}
fn serve_api_tile(request: HttpRequest) -> impl Future<Item=HttpResponse, Error=client::SendRequestError> {
let tail = request.match_info().get("tail").unwrap_or("");
client
::get(format!("http://{}/{}", TILE_API_ADDRESS, tail))
.finish()
.unwrap()
.send()
.map(
|client_response| {
HttpResponseBuilder
::from(&client_response)
.chunked()
.streaming(client_response)
}
)
}
fn serve_index(_request: HttpRequest) -> Result<NamedFile> {
let mut path: PathBuf = PathBuf::from(STATIC_DIRECTORY);
path.push("index.html");
Ok(NamedFile::open(path)?)
}
fn main() {
server
::new(
|| {
vec![
App::new()
.prefix("/static")
.resource(
r"/{tail:.*}",
|resource| {
resource.method(Method::GET).f(serve_static_files)
}
),
App::new()
.prefix("/api")
.resource(
"/geocode",
|resource| {
resource.method(Method::GET).a(serve_api_geocode)
}
)
.resource(
"/route",
|resource| {
resource.method(Method::GET).a(serve_api_route)
}
)
.resource(
"/tile/{tail:.*}",
|resource| {
resource.method(Method::GET).a(serve_api_tile)
}
),
App::new()
.prefix("/")
.resource(
"/index.html",
|resource| {
resource.method(Method::GET).f(serve_index)
}
)
.resource(
"/",
|resource| {
resource.method(Method::GET).f(serve_index)
}
)
]
}
)
.bind(SERVER_ADDRESS)
.expect(&format!("Cannot bind the server to {}.", SERVER_ADDRESS))
.shutdown_timeout(30)
.run();
}
gitextract_o8etyil8/
├── .gitignore
├── .gitmodules
├── LICENSE
├── README.md
├── justfile
├── public/
│ └── static/
│ ├── css/
│ │ └── default.css
│ ├── image/
│ │ └── README.md
│ └── javascript/
│ └── main.js
└── source/
├── api/
│ ├── geocode/
│ │ ├── indexer/
│ │ │ ├── Cargo.toml
│ │ │ └── src/
│ │ │ └── main.rs
│ │ └── searcher/
│ │ ├── Cargo.toml
│ │ └── src/
│ │ └── main.rs
│ └── tile/
│ ├── Cargo.toml
│ ├── database/
│ │ └── .keep
│ ├── diesel.toml
│ └── src/
│ ├── main.rs
│ ├── models.rs
│ └── schema.rs
├── client/
│ ├── elm.json
│ ├── src/
│ │ ├── Atlasr/
│ │ │ ├── Geocode.elm
│ │ │ ├── Map.elm
│ │ │ ├── MapboxGL/
│ │ │ │ ├── Options.elm
│ │ │ │ └── Ports.elm
│ │ │ ├── Position.elm
│ │ │ └── Route.elm
│ │ ├── Main.elm
│ │ └── index.html
│ └── tests/
│ ├── elm-package.json
│ └── unit/
│ └── Atlasr/
│ └── Test/
│ └── Position.elm
└── server/
├── Cargo.toml
└── src/
└── main.rs
SYMBOL INDEX (29 symbols across 6 files)
FILE: public/static/javascript/main.js
function guid (line 110) | function guid() {
FILE: source/api/geocode/indexer/src/main.rs
type Record (line 27) | struct Record {
method indexable (line 62) | fn indexable(&self) -> bool {
function create_schema (line 80) | fn create_schema() -> Schema {
function create_index (line 92) | fn create_index(index_directory: directory::MmapDirectory, schema: &Sche...
function index_record (line 96) | fn index_record(index_writer: &mut IndexWriter, schema: &Schema, record:...
function main (line 119) | fn main() -> Result<(), Error> {
FILE: source/api/geocode/searcher/src/main.rs
constant GEOCODE_API_ADDRESS (line 19) | const GEOCODE_API_ADDRESS: &'static str = env!("GEOCODE_API_ADDRESS");
type SearchState (line 21) | struct SearchState {
function serve_search (line 26) | fn serve_search(request: HttpRequest<SearchState>) -> HttpResponse {
function main (line 91) | fn main() {
FILE: source/api/tile/src/main.rs
constant SERVER_ADDRESS (line 19) | const SERVER_ADDRESS: &'static str = env!("SERVER_ADDRESS");
constant TILE_API_ADDRESS (line 20) | const TILE_API_ADDRESS: &'static str = env!("TILE_API_ADDRESS");
function not_found (line 22) | fn not_found() -> http::Result<http::Response<Vec<u8>>> {
function error (line 29) | fn error() -> http::Result<http::Response<String>> {
function main (line 36) | fn main() {
FILE: source/api/tile/src/models.rs
type Tiles (line 2) | pub struct Tiles {
type Metadata (line 10) | pub struct Metadata {
FILE: source/server/src/main.rs
constant STATIC_DIRECTORY (line 19) | const STATIC_DIRECTORY: &'static str = concat!(ROOT_DIRECTORY!(), "stati...
constant SERVER_ADDRESS (line 20) | const SERVER_ADDRESS: &'static str = env!("SERVER_ADDRESS");
constant ROUTE_API_ADDRESS (line 21) | const ROUTE_API_ADDRESS: &'static str = env!("ROUTE_API_ADDRESS");
constant GEOCODE_API_ADDRESS (line 22) | const GEOCODE_API_ADDRESS: &'static str = env!("GEOCODE_API_ADDRESS");
constant TILE_API_ADDRESS (line 23) | const TILE_API_ADDRESS: &'static str = env!("TILE_API_ADDRESS");
function serve_static_files (line 25) | fn serve_static_files(request: HttpRequest) -> Result<NamedFile> {
function serve_api_geocode (line 34) | fn serve_api_geocode(request: HttpRequest) -> impl Future<Item=HttpRespo...
function serve_api_route (line 50) | fn serve_api_route(request: HttpRequest) -> impl Future<Item=HttpRespons...
function serve_api_tile (line 66) | fn serve_api_tile(request: HttpRequest) -> impl Future<Item=HttpResponse...
function serve_index (line 84) | fn serve_index(_request: HttpRequest) -> Result<NamedFile> {
function main (line 92) | fn main() {
Condensed preview — 31 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (70K chars).
[
{
"path": ".gitignore",
"chars": 413,
"preview": "/public/static/css/mapbox-gl.*\n/public/static/index.html\n/public/static/javascript/*.elm.js*\n/public/static/javascript/m"
},
{
"path": ".gitmodules",
"chars": 233,
"preview": "[submodule \"source/api/graphhopper\"]\n\tpath = source/api/route\n\turl = https://github.com/atlasr-org/graphhopper\n\tbranch ="
},
{
"path": "LICENSE",
"chars": 1557,
"preview": " New BSD License\n\nCopyright © 2018, Ivan Enderlin.\nAll rights reserved.\n\nRedistribution a"
},
{
"path": "README.md",
"chars": 6832,
"preview": "# 🌍 Atlasr\n\nAtlasr is a truly open-source and free map browser. The goal is threefold:\n\n 1. Learn about all the layers "
},
{
"path": "justfile",
"chars": 6083,
"preview": "# vim: filetype=just\n\nmapbox_gl_js_version = \"0.50.0\"\nserver_address = \"localhost:8889\"\ngeocode_api_address = \"localhost"
},
{
"path": "public/static/css/default.css",
"chars": 4554,
"preview": ":root {\n --brand-color : hsla(221, 40%, 14%, 1 );\n --alt-color-lighter : hsla( 9, 93%, 100%, 1 );\n "
},
{
"path": "public/static/image/README.md",
"chars": 553,
"preview": "# `map-placeholder.svg`\n\n1. Raster a part of the map,\n2. Install [`primitive`](https://github.com/fogleman/primitive),\n3"
},
{
"path": "public/static/javascript/main.js",
"chars": 4365,
"preview": "(\n () => {\n const atlasr = Elm.Atlasr.Main.init({\n node: document.body,\n flags: 1\n "
},
{
"path": "source/api/geocode/indexer/Cargo.toml",
"chars": 250,
"preview": "[package]\nname = \"atlasr-api-geocode-indexer\"\nversion = \"0.1.0\"\nauthors = [\"Ivan Enderlin <ivan.enderlin@hoa-project.net"
},
{
"path": "source/api/geocode/indexer/src/main.rs",
"chars": 5257,
"preview": "extern crate failure;\nextern crate clap;\nextern crate csv;\n#[macro_use] extern crate serde_derive;\nextern crate tantivy;"
},
{
"path": "source/api/geocode/searcher/Cargo.toml",
"chars": 179,
"preview": "[package]\nname = \"atlasr-api-geocode-searcher\"\nversion = \"0.1.0\"\nauthors = [\"Ivan Enderlin <ivan.enderlin@hoa-project.ne"
},
{
"path": "source/api/geocode/searcher/src/main.rs",
"chars": 3353,
"preview": "extern crate actix_web;\nextern crate tantivy;\n\nuse actix_web::{\n App,\n HttpRequest,\n HttpResponse,\n http::Me"
},
{
"path": "source/api/tile/Cargo.toml",
"chars": 260,
"preview": "[package]\nname = \"atlasr-api-tile\"\nversion = \"0.1.0\"\nauthors = [\"Ivan Enderlin <ivan.enderlin@hoa-project.net>\"]\n\n[depen"
},
{
"path": "source/api/tile/database/.keep",
"chars": 0,
"preview": ""
},
{
"path": "source/api/tile/diesel.toml",
"chars": 38,
"preview": "[print_schema]\nfile = \"src/schema.rs\"\n"
},
{
"path": "source/api/tile/src/main.rs",
"chars": 7512,
"preview": "#[macro_use]\nextern crate warp;\n#[macro_use]\nextern crate diesel;\nextern crate r2d2;\nextern crate r2d2_diesel;\nextern cr"
},
{
"path": "source/api/tile/src/models.rs",
"chars": 248,
"preview": "#[derive(Debug, Queryable)]\npub struct Tiles {\n pub zoom_level: i32,\n pub tile_column: i32,\n pub tile_row: i32,"
},
{
"path": "source/api/tile/src/schema.rs",
"chars": 278,
"preview": "table! {\n tiles (zoom_level, tile_column, tile_row) {\n zoom_level -> Integer,\n tile_column -> Integer,\n"
},
{
"path": "source/client/elm.json",
"chars": 594,
"preview": "{\n \"type\": \"application\",\n \"source-directories\": [\n \"src\"\n ],\n \"elm-version\": \"0.19.0\",\n \"dependen"
},
{
"path": "source/client/src/Atlasr/Geocode.elm",
"chars": 2336,
"preview": "module Atlasr.Geocode exposing (Geocode, toGeocodes)\n\nimport Atlasr.Position exposing (NamedPosition, defaultNamedPositi"
},
{
"path": "source/client/src/Atlasr/Map.elm",
"chars": 915,
"preview": "module Atlasr.Map exposing (addMarkers, connectMarkers, create, flyTo, removeMarkers)\n\nimport Atlasr.MapboxGL.Options as"
},
{
"path": "source/client/src/Atlasr/MapboxGL/Options.elm",
"chars": 906,
"preview": "module Atlasr.MapboxGL.Options exposing (Camera, Map, default)\n\nimport Atlasr.Position exposing (Position)\n\n\ntype alias "
},
{
"path": "source/client/src/Atlasr/MapboxGL/Ports.elm",
"chars": 517,
"preview": "port module Atlasr.MapboxGL.Ports exposing (mapboxgl_add_markers, mapboxgl_connect_markers, mapboxgl_create_map, mapboxg"
},
{
"path": "source/client/src/Atlasr/Position.elm",
"chars": 928,
"preview": "module Atlasr.Position exposing (Latitude, Longitude, NamedPosition, Position, defaultName, defaultNamedPosition, defaul"
},
{
"path": "source/client/src/Atlasr/Route.elm",
"chars": 2603,
"preview": "module Atlasr.Route exposing (Route, toRoute)\n\nimport Atlasr.Position exposing (Position)\nimport Http\nimport Json.Decode"
},
{
"path": "source/client/src/Main.elm",
"chars": 7132,
"preview": "module Atlasr.Main exposing (Model, Msg(..), init, main, subscriptions, update, view)\n\nimport Array exposing (Array)\nimp"
},
{
"path": "source/client/src/index.html",
"chars": 1144,
"preview": "<!DOCTYPE html>\n\n<html>\n <head>\n <title>Atlasr</title>\n\n <meta http-equiv=\"content-type\" content=\"text/html; char"
},
{
"path": "source/client/tests/elm-package.json",
"chars": 655,
"preview": "{\n \"version\": \"0.0.0\",\n \"summary\": \"Atlasr's test suites\",\n \"repository\": \"https://github.com/atlasr-org/atlasr"
},
{
"path": "source/client/tests/unit/Atlasr/Test/Position.elm",
"chars": 840,
"preview": "module Atlasr.Test.Position exposing (..)\n\nimport Atlasr.Position as Position\nimport Test exposing (..)\nimport Expect\n\n\n"
},
{
"path": "source/server/Cargo.toml",
"chars": 168,
"preview": "[package]\nname = \"atlasr-server\"\nversion = \"0.0.1\"\nauthors = [\"Ivan Enderlin <ivan.enderlin@hoa-project.net>\"]\n\n[depende"
},
{
"path": "source/server/src/main.rs",
"chars": 4617,
"preview": "extern crate actix_web;\nextern crate futures;\n\nuse actix_web::{\n App,\n HttpRequest,\n HttpResponse,\n Result,\n"
}
]
About this extraction
This page contains the full source code of the atlasr-org/atlasr GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 31 files (63.8 KB), approximately 15.4k tokens, and a symbol index with 29 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.