2022-12-17 00:39:57 +03:00
# WARNING
This documentation is for the unreleased main branch.
The documentation for the latest release v0.6 is available [here ](https://github.com/maplibre/martin/tree/v0.6#readme ).
----
2018-01-16 15:55:05 +03:00
# Martin
2017-10-09 14:29:03 +03:00
2022-06-11 10:51:41 +03:00
[![CI ](https://github.com/maplibre/martin/workflows/CI/badge.svg )](https://github.com/maplibre/martin/actions)
![Security audit ](https://github.com/maplibre/martin/workflows/Security%20audit/badge.svg )
2022-09-27 04:48:46 +03:00
[![Docker pulls ](https://img.shields.io/docker/pulls/maplibre/martin.svg )](https://hub.docker.com/r/maplibre/martin)
2017-10-15 14:48:23 +03:00
2018-10-20 12:24:28 +03:00
Martin is a [PostGIS ](https://github.com/postgis/postgis ) [vector tiles ](https://github.com/mapbox/vector-tile-spec ) server suitable for large databases. Martin is written in [Rust ](https://github.com/rust-lang/rust ) using [Actix ](https://github.com/actix/actix-web ) web framework.
2017-10-09 14:29:03 +03:00
2022-06-11 10:51:41 +03:00
![Martin ](https://raw.githubusercontent.com/maplibre/martin/main/logo.png )
2018-10-19 19:11:44 +03:00
2019-01-28 11:16:24 +03:00
- [Requirements ](#requirements )
- [Installation ](#installation )
- [Usage ](#usage )
2019-07-03 17:29:38 +03:00
- [API ](#api )
2022-10-07 20:58:30 +03:00
- [Using with MapLibre GL JS ](#using-with-maplibre )
2021-02-23 12:01:45 +03:00
- [Using with Leaflet ](#using-with-leaflet )
2021-10-07 17:00:21 +03:00
- [Using with deck.gl ](#using-with-deckgl )
2022-11-26 12:46:40 +03:00
- [Sources List ](#source-list )
2019-01-28 11:16:24 +03:00
- [Table Sources ](#table-sources )
- [Table Source TileJSON ](#table-source-tilejson )
2019-02-04 13:39:57 +03:00
- [Table Source Tiles ](#table-source-tiles )
2021-04-24 20:19:37 +03:00
- [Composite Sources ](#composite-sources )
- [Composite Source TileJSON ](#composite-source-tilejson )
- [Composite Source Tiles ](#composite-source-tiles )
2019-01-28 11:16:24 +03:00
- [Function Sources ](#function-sources )
- [Function Source TileJSON ](#function-source-tilejson )
- [Function Source Tiles ](#function-source-tiles )
- [Command-line Interface ](#command-line-interface )
- [Environment Variables ](#environment-variables )
2019-02-04 13:39:57 +03:00
- [Configuration File ](#configuration-file )
2019-01-28 11:16:24 +03:00
- [Using with Docker ](#using-with-docker )
2019-08-28 14:04:15 +03:00
- [Using with Docker Compose ](#using-with-docker-compose )
2019-02-04 13:14:51 +03:00
- [Using with Nginx ](#using-with-nginx )
2021-10-07 17:00:21 +03:00
- [Rewriting URLs ](#rewriting-urls )
- [Caching tiles ](#caching-tiles )
2019-01-28 11:16:24 +03:00
- [Building from Source ](#building-from-source )
2019-02-04 20:22:02 +03:00
- [Debugging ](#debugging )
2019-01-28 11:16:24 +03:00
- [Development ](#development )
2021-10-18 14:42:11 +03:00
- [Recipes ](#recipes )
- [Using with DigitalOcean PostgreSQL ](#using-with-digitalocean-postgresql )
- [Using with Heroku PostgreSQL ](#using-with-heroku-postgresql )
2019-01-28 11:16:24 +03:00
2019-01-09 10:01:03 +03:00
## Requirements
2022-11-30 19:57:27 +03:00
Martin requires PostGIS >= 3.0.0
2019-01-09 10:01:03 +03:00
2017-10-09 14:29:03 +03:00
## Installation
2022-11-26 12:46:40 +03:00
You can download martin from [GitHub releases page ](https://github.com/maplibre/martin/releases ).
2018-10-10 16:32:24 +03:00
2022-09-27 04:48:46 +03:00
| Platform | Downloads (latest) |
|----------|------------------------|
| Linux | [64-bit][rl-linux-tar] |
| macOS | [64-bit][rl-macos-tar] |
| Windows | [64-bit][rl-win64-zip] |
2019-09-30 18:54:25 +03:00
2022-06-11 10:51:41 +03:00
[rl-linux-tar]: https://github.com/maplibre/martin/releases/latest/download/martin-Linux-x86_64.tar.gz
[rl-macos-tar]: https://github.com/maplibre/martin/releases/latest/download/martin-Darwin-x86_64.tar.gz
2022-09-27 04:48:46 +03:00
[rl-win64-zip]: https://github.com/maplibre/martin/releases/latest/download/martin-Windows-x86_64.zip
2019-09-30 18:54:25 +03:00
2018-10-17 11:49:02 +03:00
If you are using macOS and [Homebrew ](https://brew.sh/ ) you can install martin using Homebrew tap.
2018-10-10 16:32:24 +03:00
```shell
2018-10-10 17:23:41 +03:00
brew tap urbica/tap
2018-10-10 16:32:24 +03:00
brew install martin
```
2017-10-09 14:29:03 +03:00
2022-09-27 04:48:46 +03:00
You can also use [official Docker image ](https://hub.docker.com/r/maplibre/martin )
2019-09-30 18:54:25 +03:00
```shell
2022-12-22 09:35:29 +03:00
docker run -p 3000:3000 -e DATABASE_URL=postgresql://postgres@localhost/db maplibre/martin
2019-09-30 18:54:25 +03:00
```
2017-10-09 14:29:03 +03:00
## Usage
2018-10-10 19:27:21 +03:00
Martin requires a database connection string. It can be passed as a command-line argument or as a `DATABASE_URL` environment variable.
2018-10-10 16:32:24 +03:00
```shell
2022-12-22 09:35:29 +03:00
martin postgresql://postgres@localhost/db
2018-10-10 16:32:24 +03:00
```
2017-10-09 14:29:03 +03:00
2021-04-24 20:19:37 +03:00
Martin provides [TileJSON ](https://github.com/mapbox/tilejson-spec ) endpoint for each [geospatial-enabled ](https://postgis.net/docs/postgis_usage.html#geometry_columns ) table in your database.
2019-07-03 17:29:38 +03:00
## API
2021-10-21 12:20:33 +03:00
When started, martin will go through all spatial tables and functions with an appropriate signature in the database. These tables and functions will be available as the HTTP endpoints, which you can use to query Mapbox vector tiles.
2022-11-26 12:46:40 +03:00
| Method | URL | Description |
|--------|------------------------------------|---------------------------------------------------------|
| `GET` | `/` | Status text, that will eventually show web UI |
| `GET` | `/catalog` | [List of all sources ](#source-list ) |
| `GET` | `/{name}` | [Source TileJSON ](#table-source-tilejson ) |
| `GET` | `/{name}/{z}/{x}/{y}` | [Source Tiles ](#table-source-tiles ) |
| `GET` | `/{name1},...,{nameN}` | [Composite Source TileJSON ](#composite-source-tilejson ) |
| `GET` | `/{name1},...,{nameN}/{z}/{x}/{y}` | [Composite Source Tiles ](#composite-source-tiles ) |
| `GET` | `/health` | Martin server health check: returns 200 `OK` |
2019-07-03 17:29:38 +03:00
2022-10-07 20:58:30 +03:00
## Using with MapLibre
[MapLibre ](https://maplibre.org/projects/maplibre-gl-js/ ) is an Open-source JavaScript library for showing maps on a website. MapLibre can accept [MVT vector tiles ](https://github.com/mapbox/vector-tile-spec ) generated by Martin, and applies [a style ](https://maplibre.org/maplibre-gl-js-docs/style-spec/ ) to them to draw a map using Web GL.
2019-02-04 13:39:57 +03:00
2022-11-26 12:46:40 +03:00
You can add a layer to the map and specify Martin [TileJSON ](https://github.com/mapbox/tilejson-spec ) endpoint as a vector source URL. You should also specify a `source-layer` property. For [Table Sources ](#table-sources ) it is `{table_name}` by default.
2019-02-04 13:39:57 +03:00
```js
map.addLayer({
2022-11-26 12:46:40 +03:00
id: 'points',
2021-10-07 17:00:21 +03:00
type: 'circle',
2019-02-04 13:39:57 +03:00
source: {
2021-10-07 17:00:21 +03:00
type: 'vector',
2022-11-26 12:46:40 +03:00
url: 'http://localhost:3000/points'
2021-02-23 12:01:45 +03:00
},
2022-11-26 12:46:40 +03:00
'source-layer': 'points',
2021-02-23 12:01:45 +03:00
paint: {
2021-10-07 17:00:21 +03:00
'circle-color': 'red'
2022-10-07 20:58:30 +03:00
},
});
```
```js
map.addSource('rpc', {
type: 'vector',
2022-11-30 19:57:27 +03:00
url: `http://localhost:3000/function_zxy_query`
2022-10-07 20:58:30 +03:00
});
map.addLayer({
id: 'points',
type: 'circle',
source: 'rpc',
2022-11-30 19:57:27 +03:00
'source-layer': 'function_zxy_query',
2022-10-07 20:58:30 +03:00
paint: {
'circle-color': 'blue'
},
2019-02-04 13:39:57 +03:00
});
```
2022-11-26 12:46:40 +03:00
You can also combine multiple sources into one source with [Composite Sources ](#composite-sources ). Each source in a composite source can be accessed with its `{source_name}` as a `source-layer` property.
2021-04-24 20:19:37 +03:00
```js
2021-10-07 17:00:21 +03:00
map.addSource('points', {
type: 'vector',
2022-11-26 12:46:40 +03:00
url: `http://0.0.0.0:3000/points1,points2`
2021-04-24 20:19:37 +03:00
});
map.addLayer({
2021-10-07 17:00:21 +03:00
id: 'red_points',
type: 'circle',
source: 'points',
2022-11-26 12:46:40 +03:00
'source-layer': 'points1',
2021-04-24 20:19:37 +03:00
paint: {
2021-10-07 17:00:21 +03:00
'circle-color': 'red'
}
2021-04-24 20:19:37 +03:00
});
map.addLayer({
2021-10-07 17:00:21 +03:00
id: 'blue_points',
type: 'circle',
source: 'points',
2022-11-26 12:46:40 +03:00
'source-layer': 'points2',
2021-04-24 20:19:37 +03:00
paint: {
2021-10-07 17:00:21 +03:00
'circle-color': 'blue'
}
2021-04-24 20:19:37 +03:00
});
```
2021-02-23 12:01:45 +03:00
## Using with Leaflet
[Leaflet ](https://github.com/Leaflet/Leaflet ) is the leading open-source JavaScript library for mobile-friendly interactive maps.
You can add vector tiles using [Leaflet.VectorGrid ](https://github.com/Leaflet/Leaflet.VectorGrid ) plugin. You must initialize a [VectorGrid.Protobuf ](https://leaflet.github.io/Leaflet.VectorGrid/vectorgrid-api-docs.html#vectorgrid-protobuf ) with a URL template, just like in L.TileLayers. The difference is that you should define the styling for all the features.
```js
L.vectorGrid
2022-11-26 12:46:40 +03:00
.protobuf('http://localhost:3000/points/{z}/{x}/{y}', {
2021-02-23 12:01:45 +03:00
vectorTileLayerStyles: {
2022-11-26 12:46:40 +03:00
'points': {
2021-02-23 12:01:45 +03:00
color: 'red',
2021-10-07 17:00:21 +03:00
fill: true
}
}
2021-02-23 12:01:45 +03:00
})
.addTo(map);
```
2021-02-25 13:33:44 +03:00
## Using with deck.gl
[deck.gl ](https://deck.gl/ ) is a WebGL-powered framework for visual exploratory data analysis of large datasets.
You can add vector tiles using [MVTLayer ](https://deck.gl/docs/api-reference/geo-layers/mvt-layer ). MVTLayer `data` property defines the remote data for the MVT layer. It can be
2021-10-07 17:00:21 +03:00
- `String` : Either a URL template or a [TileJSON ](https://github.com/mapbox/tilejson-spec ) URL.
- `Array` : an array of URL templates. It allows to balance the requests across different tile endpoints. For example, if you define an array with 4 urls and 16 tiles need to be loaded, each endpoint is responsible to server 16/4 tiles.
- `JSON` : A valid [TileJSON object ](https://github.com/mapbox/tilejson-spec/tree/master/2.2.0 ).
2021-02-25 13:33:44 +03:00
```js
const pointsLayer = new MVTLayer({
2022-11-26 12:46:40 +03:00
data: 'http://localhost:3000/points', // 'http://localhost:3000/table_source/{z}/{x}/{y}'
2021-02-25 13:33:44 +03:00
pointRadiusUnits: 'pixels',
getRadius: 5,
getFillColor: [230, 0, 0]
2021-02-25 16:07:57 +03:00
});
2021-02-25 13:33:44 +03:00
2021-02-25 16:07:57 +03:00
const deckgl = new DeckGL({
container: 'map',
mapStyle: 'https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json',
initialViewState: {
latitude: 0,
longitude: 0,
zoom: 1
},
layers: [pointsLayer]
});
2021-02-25 13:33:44 +03:00
```
2022-10-07 20:58:30 +03:00
## Using with Mapbox
[Mapbox GL JS ](https://github.com/mapbox/mapbox-gl-js ) is a JavaScript library for interactive, customizable vector maps on the web. Mapbox GL JS v1.x was open source, and it was forked as MapLibre (see [above ](#using-with-maplibre )), so using Martin with Mapbox is similar to MapLibre. Mapbox GL JS can accept [MVT vector tiles ](https://github.com/mapbox/vector-tile-spec ) generated by Martin, and applies [a style ](https://docs.mapbox.com/mapbox-gl-js/style-spec/ ) to them to draw a map using Web GL.
2022-11-26 12:46:40 +03:00
You can add a layer to the map and specify martin TileJSON endpoint as a vector source URL. You should also specify a `source-layer` property. For [Table Sources ](#table-sources ) it is `{table_name}` by default.
2022-10-07 20:58:30 +03:00
```js
map.addLayer({
2022-11-26 12:46:40 +03:00
id: 'points',
2022-10-07 20:58:30 +03:00
type: 'circle',
source: {
type: 'vector',
2022-11-26 12:46:40 +03:00
url: 'http://localhost:3000/points'
2022-10-07 20:58:30 +03:00
},
2022-11-26 12:46:40 +03:00
'source-layer': 'points',
2022-10-07 20:58:30 +03:00
paint: {
'circle-color': 'red'
}
});
```
2022-11-26 12:46:40 +03:00
## Source List
2018-10-11 15:01:47 +03:00
2022-11-26 12:46:40 +03:00
A list of all available sources is available in a catalogue:
2018-10-11 15:01:47 +03:00
```shell
2022-11-26 12:46:40 +03:00
curl localhost:3000/catalog | jq
2018-10-11 15:01:47 +03:00
```
2022-11-26 12:46:40 +03:00
```yaml
[
{
2022-11-30 19:57:27 +03:00
"id": "function_zxy_query",
"name": "public.function_zxy_query"
2022-11-26 12:46:40 +03:00
},
{
"id": "points1",
"name": "public.points1.geom"
},
// ...
]
```
2018-10-11 15:01:47 +03:00
2022-11-26 12:46:40 +03:00
## Table Sources
2018-10-11 15:01:47 +03:00
2022-11-26 12:46:40 +03:00
Table Source is a database table which can be used to query [vector tiles ](https://github.com/mapbox/vector-tile-spec ). When started, martin will go through all spatial tables in the database and build a list of table sources. A table should have at least one geometry column with non-zero SRID. All other table columns will be represented as properties of a vector tile feature.
2018-10-11 15:01:47 +03:00
2022-11-26 12:46:40 +03:00
### Table Source TileJSON
2018-10-11 15:01:47 +03:00
2022-11-26 12:46:40 +03:00
Table Source [TileJSON ](https://github.com/mapbox/tilejson-spec ) endpoint is available at `/{table_name}` .
For example, `points` table will be available at `/points` , unless there is another source with the same name, or if the table has multiple geometry columns, in which case it will be available at `/points.1` , `/points.2` , etc.
2021-10-21 12:20:33 +03:00
```shell
2022-11-26 12:46:40 +03:00
curl localhost:3000/points
2021-10-21 12:20:33 +03:00
```
2019-02-04 13:39:57 +03:00
### Table Source Tiles
2018-10-11 15:01:47 +03:00
2022-11-26 12:46:40 +03:00
Table Source tiles endpoint is available at `/{table_name}/{z}/{x}/{y}`
2018-10-11 15:01:47 +03:00
2022-11-26 12:46:40 +03:00
For example, `points` table will be available at `/points/{z}/{x}/{y}`
2018-10-11 15:01:47 +03:00
```shell
2022-11-26 12:46:40 +03:00
curl localhost:3000/points/0/0/0
2018-10-11 15:01:47 +03:00
```
2021-10-21 12:20:33 +03:00
In case if you have multiple geometry columns in that table and want to access a particular geometry column in vector tile, you should also specify the geometry column in the table source name
```shell
2022-11-26 12:46:40 +03:00
curl localhost:3000/points.geom/0/0/0
2021-10-21 12:20:33 +03:00
```
2021-04-24 20:19:37 +03:00
## Composite Sources
2022-11-26 12:46:40 +03:00
Composite Sources allows combining multiple sources into one. Composite Source consists of multiple sources separated by comma `{source1},...,{sourceN}`
2021-04-24 20:19:37 +03:00
2022-11-26 12:46:40 +03:00
Each source in a composite source can be accessed with its `{source_name}` as a `source-layer` property.
2021-04-24 20:19:37 +03:00
### Composite Source TileJSON
2022-11-26 12:46:40 +03:00
Composite Source [TileJSON ](https://github.com/mapbox/tilejson-spec ) endpoint is available at `/{source1},...,{sourceN}` .
2021-04-24 20:19:37 +03:00
2022-11-26 12:46:40 +03:00
For example, composite source for `points` and `lines` tables will be available at `/points,lines`
2021-04-24 20:19:37 +03:00
```shell
2022-11-26 12:46:40 +03:00
curl localhost:3000/points,lines
2021-04-24 20:19:37 +03:00
```
### Composite Source Tiles
2022-11-26 12:46:40 +03:00
Composite Source tiles endpoint is available at `/{source1},...,{sourceN}/{z}/{x}/{y}`
2021-04-24 20:19:37 +03:00
2022-11-26 12:46:40 +03:00
For example, composite source for `points` and `lines` tables will be available at `/points,lines/{z}/{x}/{y}`
2021-04-24 20:19:37 +03:00
```shell
2022-11-26 12:46:40 +03:00
curl localhost:3000/points,lines/0/0/0
2021-04-24 20:19:37 +03:00
```
2018-10-12 18:18:34 +03:00
## Function Sources
Support z,x,y and record-returning funcs, table rework (#380)
Can now handle several additional Postgres functions to get a tile, plus
tons of small fixes
### Multiple result variants
* `getmvt(z,x,y) -> [bytea,md5]` (single row with two columns)
* `getmvt(z,x,y) -> [bytea]` (single row with a single column)
* `getmvt(z,x,y) -> bytea` (value)
### Multiple input parameter variants
* `getmvt(z, x, y)` or `getmvt(zoom, x, y)` (all 3 vars must be
integers)
* `getmvt(z, x, y, url_query)`, where instead of `url_query` it could be
any other name, but must be of type JSON
### Breaking
* srid is now the same type as PG -- `i32`
* renamed config vals `table_sources` and `function_sources` into
`tables` and `functions`
### Features and fixes
* if postgis is v3.1+, uses margin parameter to extend the search box by
the size of the buffer. I think we should make 3.1 minimal required.
* fixes feature ID issue from #466
* fixes mixed case names for schemas, tables and columns, functions and
parameter names per #389
### Notes
* More dynamic SQL generation in code instead of using external SQL
files. Those should only be used when they are not parametrized.
* The new function/table discovery mechanism: query for all functions in
the database, and match up those functions with the ones configured (if
any), plus adds all the rest of the un-declared ones if discovery mode
is on.
* During table and function discovery, the code generates a map of
`(PgSqlInfo, FunctionInfo)` (or table) tupples containing SQL needed to
get the tile.
* Auto-discovery mode is currently hidden - the discovery is on only
when no tables or functions are configured. TBD - how to configure it in
the future
* The new system allows for an easy way to auto-discover for the
specific schemas only, solving #47
* predictable order of table/function instantiation
* bounding boxes computed in parallel for all tables (when not
configured)
* proper identifier escaping
* test cleanup
fixes #378
fixes #466
fixes #65
fixes #389
2022-12-10 17:20:42 +03:00
Function Source is a database function which can be used to query [vector tiles ](https://github.com/mapbox/vector-tile-spec ). When started, martin will look for the functions with a suitable signature. A function that takes `z integer` (or `zoom integer` ), `x integer` , `y integer` , and an optional `query json` and returns `bytea` , can be used as a Function Source. Alternatively the function could return a record with a single `bytea` field, or a record with two fields of types `bytea` and `text` , where the `text` field is a etag key (i.e. md5 hash).
2018-10-12 18:18:34 +03:00
Support z,x,y and record-returning funcs, table rework (#380)
Can now handle several additional Postgres functions to get a tile, plus
tons of small fixes
### Multiple result variants
* `getmvt(z,x,y) -> [bytea,md5]` (single row with two columns)
* `getmvt(z,x,y) -> [bytea]` (single row with a single column)
* `getmvt(z,x,y) -> bytea` (value)
### Multiple input parameter variants
* `getmvt(z, x, y)` or `getmvt(zoom, x, y)` (all 3 vars must be
integers)
* `getmvt(z, x, y, url_query)`, where instead of `url_query` it could be
any other name, but must be of type JSON
### Breaking
* srid is now the same type as PG -- `i32`
* renamed config vals `table_sources` and `function_sources` into
`tables` and `functions`
### Features and fixes
* if postgis is v3.1+, uses margin parameter to extend the search box by
the size of the buffer. I think we should make 3.1 minimal required.
* fixes feature ID issue from #466
* fixes mixed case names for schemas, tables and columns, functions and
parameter names per #389
### Notes
* More dynamic SQL generation in code instead of using external SQL
files. Those should only be used when they are not parametrized.
* The new function/table discovery mechanism: query for all functions in
the database, and match up those functions with the ones configured (if
any), plus adds all the rest of the un-declared ones if discovery mode
is on.
* During table and function discovery, the code generates a map of
`(PgSqlInfo, FunctionInfo)` (or table) tupples containing SQL needed to
get the tile.
* Auto-discovery mode is currently hidden - the discovery is on only
when no tables or functions are configured. TBD - how to configure it in
the future
* The new system allows for an easy way to auto-discover for the
specific schemas only, solving #47
* predictable order of table/function instantiation
* bounding boxes computed in parallel for all tables (when not
configured)
* proper identifier escaping
* test cleanup
fixes #378
fixes #466
fixes #65
fixes #389
2022-12-10 17:20:42 +03:00
| Argument | Type | Description |
|----------------------------|---------|-------------------------|
| z (or zoom) | integer | Tile zoom parameter |
| x | integer | Tile x parameter |
| y | integer | Tile y parameter |
| query (optional, any name) | json | Query string parameters |
2018-10-12 18:18:34 +03:00
2022-11-26 12:46:40 +03:00
For example, if you have a table `table_source` in WGS84 (`4326` SRID), then you can use this function as a Function Source:
2018-10-12 18:18:34 +03:00
Support z,x,y and record-returning funcs, table rework (#380)
Can now handle several additional Postgres functions to get a tile, plus
tons of small fixes
### Multiple result variants
* `getmvt(z,x,y) -> [bytea,md5]` (single row with two columns)
* `getmvt(z,x,y) -> [bytea]` (single row with a single column)
* `getmvt(z,x,y) -> bytea` (value)
### Multiple input parameter variants
* `getmvt(z, x, y)` or `getmvt(zoom, x, y)` (all 3 vars must be
integers)
* `getmvt(z, x, y, url_query)`, where instead of `url_query` it could be
any other name, but must be of type JSON
### Breaking
* srid is now the same type as PG -- `i32`
* renamed config vals `table_sources` and `function_sources` into
`tables` and `functions`
### Features and fixes
* if postgis is v3.1+, uses margin parameter to extend the search box by
the size of the buffer. I think we should make 3.1 minimal required.
* fixes feature ID issue from #466
* fixes mixed case names for schemas, tables and columns, functions and
parameter names per #389
### Notes
* More dynamic SQL generation in code instead of using external SQL
files. Those should only be used when they are not parametrized.
* The new function/table discovery mechanism: query for all functions in
the database, and match up those functions with the ones configured (if
any), plus adds all the rest of the un-declared ones if discovery mode
is on.
* During table and function discovery, the code generates a map of
`(PgSqlInfo, FunctionInfo)` (or table) tupples containing SQL needed to
get the tile.
* Auto-discovery mode is currently hidden - the discovery is on only
when no tables or functions are configured. TBD - how to configure it in
the future
* The new system allows for an easy way to auto-discover for the
specific schemas only, solving #47
* predictable order of table/function instantiation
* bounding boxes computed in parallel for all tables (when not
configured)
* proper identifier escaping
* test cleanup
fixes #378
fixes #466
fixes #65
fixes #389
2022-12-10 17:20:42 +03:00
```sql, ignore
CREATE OR REPLACE FUNCTION function_zxy_query(z integer, x integer, y integer) RETURNS bytea AS $$
DECLARE
mvt bytea;
BEGIN
SELECT INTO mvt ST_AsMVT(tile, 'function_zxy_query', 4096, 'geom') FROM (
SELECT
ST_AsMVTGeom(ST_Transform(ST_CurveToLine(geom), 3857), ST_TileEnvelope(z, x, y), 4096, 64, true) AS geom
FROM table_source
WHERE geom & & ST_Transform(ST_TileEnvelope(z, x, y), 4326)
) as tile WHERE geom IS NOT NULL;
RETURN mvt;
END
$$ LANGUAGE plpgsql IMMUTABLE STRICT PARALLEL SAFE;
```
2022-11-26 12:46:40 +03:00
```sql, ignore
2022-11-30 19:57:27 +03:00
CREATE OR REPLACE FUNCTION function_zxy_query(z integer, x integer, y integer, query_params json) RETURNS bytea AS $$
2018-10-12 18:18:34 +03:00
DECLARE
2019-10-14 16:57:19 +03:00
mvt bytea;
2018-10-12 18:18:34 +03:00
BEGIN
2022-11-30 19:57:27 +03:00
SELECT INTO mvt ST_AsMVT(tile, 'function_zxy_query', 4096, 'geom') FROM (
2018-10-12 18:18:34 +03:00
SELECT
2022-11-30 19:57:27 +03:00
ST_AsMVTGeom(ST_Transform(ST_CurveToLine(geom), 3857), ST_TileEnvelope(z, x, y), 4096, 64, true) AS geom
2022-11-26 12:46:40 +03:00
FROM table_source
2022-11-30 19:57:27 +03:00
WHERE geom & & ST_Transform(ST_TileEnvelope(z, x, y), 4326)
2018-10-12 18:18:34 +03:00
) as tile WHERE geom IS NOT NULL;
RETURN mvt;
END
$$ LANGUAGE plpgsql IMMUTABLE STRICT PARALLEL SAFE;
```
2019-10-14 16:57:19 +03:00
The `query_params` argument is a JSON representation of the tile request query params. For example, if user requested a tile with [urlencoded ](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent ) params:
```shell
curl \
--data-urlencode 'arrayParam=[1, 2, 3]' \
--data-urlencode 'numberParam=42' \
--data-urlencode 'stringParam=value' \
--data-urlencode 'booleanParam=true' \
--data-urlencode 'objectParam={"answer" : 42}' \
2022-11-30 19:57:27 +03:00
--get localhost:3000/function_zxy_query/0/0/0
2019-10-14 16:57:19 +03:00
```
then `query_params` will be parsed as:
```json
{
"arrayParam": [1, 2, 3],
"numberParam": 42,
"stringParam": "value",
"booleanParam": true,
"objectParam": { "answer": 42 }
}
```
You can access this params using [json operators ](https://www.postgresql.org/docs/current/functions-json.html ):
2022-11-26 12:46:40 +03:00
```sql, ignore
2019-10-14 16:57:19 +03:00
...WHERE answer = (query_params->'objectParam'->>'answer')::int;
```
2018-10-11 15:01:47 +03:00
### Function Source TileJSON
2022-11-26 12:46:40 +03:00
Function Source [TileJSON ](https://github.com/mapbox/tilejson-spec ) endpoint is available at `/{function_name}`
2018-10-11 15:01:47 +03:00
2022-11-26 12:46:40 +03:00
For example, `points` function will be available at `/points`
2018-10-11 15:01:47 +03:00
```shell
2022-11-26 12:46:40 +03:00
curl localhost:3000/points
2018-10-11 15:01:47 +03:00
```
2018-10-17 11:49:02 +03:00
### Function Source Tiles
2018-10-11 15:01:47 +03:00
2022-11-26 12:46:40 +03:00
Function Source tiles endpoint is available at `/{function_name}/{z}/{x}/{y}`
2018-10-11 15:01:47 +03:00
2022-11-26 12:46:40 +03:00
For example, `points` function will be available at `/points/{z}/{x}/{y}`
2018-10-11 15:01:47 +03:00
```shell
2022-11-26 12:46:40 +03:00
curl localhost:3000/points/0/0/0
2018-10-11 15:01:47 +03:00
```
2018-10-17 11:49:02 +03:00
## Command-line Interface
2018-10-11 15:01:47 +03:00
You can configure martin using command-line interface
2018-10-10 19:27:21 +03:00
```shell
2022-10-07 20:58:30 +03:00
Usage: martin [OPTIONS] [CONNECTION]
Arguments:
[CONNECTION] Database connection string
2018-10-10 19:27:21 +03:00
Options:
2022-10-07 20:58:30 +03:00
-c, --config < CONFIG >
Path to config file
-k, --keep-alive < KEEP_ALIVE >
Connection keep alive timeout. [DEFAULT: 75]
-l, --listen-addresses < LISTEN_ADDRESSES >
The socket address to bind. [DEFAULT: 0.0.0.0:3000]
-W, --workers < WORKERS >
Number of web server workers
--ca-root-file < CA_ROOT_FILE >
Loads trusted root certificates from a file. The file should contain a sequence of PEM-formatted CA certificates
--danger-accept-invalid-certs
Trust invalid certificates. This introduces significant vulnerabilities, and should only be used as a last resort
-d, --default-srid < DEFAULT_SRID >
If a spatial table has SRID 0, then this default SRID will be used as a fallback
-p, --pool-size < POOL_SIZE >
Maximum connections pool size [DEFAULT: 20]
-h, --help
Print help information
-V, --version
Print version information
2018-10-10 19:27:21 +03:00
```
2018-10-17 11:49:02 +03:00
## Environment Variables
2018-03-27 14:40:33 +03:00
2018-10-11 15:01:47 +03:00
You can also configure martin using environment variables
2018-10-10 16:32:24 +03:00
2022-12-22 09:35:29 +03:00
| Environment variable | Example | Description |
|-------------------------------|--------------------------------------|---------------------------------------------|
| `DATABASE_URL` | `postgresql://postgres@localhost/db` | Postgres database connection |
| `CA_ROOT_FILE` | `./ca-certificate.crt` | Loads trusted root certificates from a file |
| `DEFAULT_SRID` | `4326` | Fallback SRID |
| `DANGER_ACCEPT_INVALID_CERTS` | `false` | Trust invalid certificates |
2018-03-27 14:40:33 +03:00
2019-02-04 13:39:57 +03:00
## Configuration File
If you don't want to expose all of your tables and functions, you can list your sources in a configuration file. To start martin with a configuration file you need to pass a path to a file with a `--config` argument.
```shell
martin --config config.yaml
```
2022-06-11 10:51:41 +03:00
You can find an example of a configuration file [here ](https://github.com/maplibre/martin/blob/main/tests/config.yaml ).
2019-02-04 13:39:57 +03:00
```yaml
# Connection keep alive timeout [default: 75]
keep_alive: 75
2022-09-28 11:19:23 +03:00
# The socket address to bind [default: 0.0.0.0:3000]
listen_addresses: '0.0.0.0:3000'
2019-02-04 13:39:57 +03:00
2022-09-28 11:19:23 +03:00
# Number of web server workers
worker_processes: 8
2020-06-02 09:49:21 +03:00
2022-12-22 09:35:29 +03:00
# Database configuration. This can also be a list of PG configs.
postgres:
# Database connection string
connection_string: 'postgresql://postgres@localhost:5432/db'
# If a spatial table has SRID 0, then this SRID will be used as a fallback
default_srid: 4326
# Maximum connections pool size [default: 20]
pool_size: 20
# Associative arrays of table sources
tables:
table_source_id:
# Table schema (required)
schema: public
# Table name (required)
table: table_source
# Geometry SRID (required)
srid: 4326
# Geometry column name (required)
geometry_column: geom
# Feature id column name
id_column: ~
# An integer specifying the minimum zoom level
minzoom: 0
# An integer specifying the maximum zoom level. MUST be >= minzoom
maxzoom: 30
# The maximum extent of available map tiles. Bounds MUST define an area
# covered by all zoom levels. The bounds are represented in WGS:84
# latitude and longitude values, in the order left, bottom, right, top.
# Values may be integers or floating point numbers.
bounds: [-180.0, -90.0, 180.0, 90.0]
# Tile extent in tile coordinate space
extent: 4096
# Buffer distance in tile coordinate space to optionally clip geometries
buffer: 64
# Boolean to control if geometries should be clipped or encoded as is
clip_geom: true
# Geometry type
geometry_type: GEOMETRY
# List of columns, that should be encoded as tile properties (required)
properties:
gid: int4
# Associative arrays of function sources
functions:
function_source_id:
# Schema name (required)
schema: public
# Function name (required)
function: function_zxy_query
# An integer specifying the minimum zoom level
minzoom: 0
# An integer specifying the maximum zoom level. MUST be >= minzoom
maxzoom: 30
# The maximum extent of available map tiles. Bounds MUST define an area
# covered by all zoom levels. The bounds are represented in WGS:84
# latitude and longitude values, in the order left, bottom, right, top.
# Values may be integers or floating point numbers.
bounds: [-180.0, -90.0, 180.0, 90.0]
2019-02-04 13:39:57 +03:00
```
2017-10-09 14:29:03 +03:00
## Using with Docker
2022-09-27 04:48:46 +03:00
You can use official Docker image [`maplibre/martin` ](https://hub.docker.com/r/maplibre/martin )
2019-02-04 13:00:37 +03:00
```shell
docker run \
-p 3000:3000 \
2022-12-22 09:35:29 +03:00
-e DATABASE_URL=postgresql://postgres@localhost/db \
2022-09-27 04:48:46 +03:00
maplibre/martin
2019-02-04 13:00:37 +03:00
```
If you are running PostgreSQL instance on `localhost` , you have to change network settings to allow the Docker container to access the `localhost` network.
For Linux, add the `--net=host` flag to access the `localhost` PostgreSQL service.
2018-10-10 16:32:24 +03:00
```shell
docker run \
2019-02-04 13:00:37 +03:00
--net=host \
2018-10-10 16:32:24 +03:00
-p 3000:3000 \
2022-12-22 09:35:29 +03:00
-e DATABASE_URL=postgresql://postgres@localhost/db \
2022-09-27 04:48:46 +03:00
maplibre/martin
2018-10-10 16:32:24 +03:00
```
2019-02-04 13:00:37 +03:00
For macOS, use `host.docker.internal` as hostname to access the `localhost` PostgreSQL service.
```shell
docker run \
-p 3000:3000 \
2022-12-22 09:35:29 +03:00
-e DATABASE_URL=postgresql://postgres@host.docker.internal/db \
2022-09-27 04:48:46 +03:00
maplibre/martin
2019-02-04 13:00:37 +03:00
```
For Windows, use `docker.for.win.localhost` as hostname to access the `localhost` PostgreSQL service.
```shell
docker run \
-p 3000:3000 \
2022-12-22 09:35:29 +03:00
-e DATABASE_URL=postgresql://postgres@docker.for.win.localhost/db \
2022-09-27 04:48:46 +03:00
maplibre/martin
2019-02-04 13:00:37 +03:00
```
2019-08-28 14:04:15 +03:00
## Using with Docker Compose
2022-06-11 10:51:41 +03:00
You can use example [`docker-compose.yml` ](https://raw.githubusercontent.com/maplibre/martin/main/docker-compose.yml ) file as a reference
2019-08-28 14:04:15 +03:00
```yml
2021-10-07 17:00:21 +03:00
version: '3'
2019-08-28 14:04:15 +03:00
services:
martin:
2022-12-14 04:14:07 +03:00
image: maplibre/martin:v0.6.2
2019-08-28 14:04:15 +03:00
restart: unless-stopped
ports:
2022-08-11 14:06:12 +03:00
- "3000:3000"
2019-08-28 14:04:15 +03:00
environment:
2022-12-22 09:35:29 +03:00
- DATABASE_URL=postgresql://postgresql:password@db/db
2019-08-28 14:04:15 +03:00
depends_on:
- db
db:
2022-12-04 08:34:44 +03:00
image: postgis/postgis:14-3.3-alpine
2019-08-28 14:04:15 +03:00
restart: unless-stopped
environment:
- POSTGRES_DB=db
- POSTGRES_USER=postgres
2020-12-27 16:35:05 +03:00
- POSTGRES_PASSWORD=password
2019-08-28 14:04:15 +03:00
volumes:
- ./pg_data:/var/lib/postgresql/data
```
First, you need to start `db` service
```shell
docker-compose up -d db
```
Then, after `db` service is ready to accept connections, you can start `martin`
```shell
docker-compose up -d martin
```
2022-11-26 12:46:40 +03:00
By default, martin will be available at [localhost:3000 ](http://localhost:3000/ )
2019-08-28 14:04:15 +03:00
2019-02-04 13:13:16 +03:00
## Using with Nginx
2021-10-07 17:00:21 +03:00
You can run martin behind Nginx proxy, so you can cache frequently accessed tiles and reduce unnecessary pressure on the database.
```yml
version: '3'
services:
nginx:
image: nginx:alpine
restart: unless-stopped
ports:
2022-08-11 14:06:12 +03:00
- "80:80"
2021-10-07 17:00:21 +03:00
volumes:
- ./cache:/var/cache/nginx
- ./nginx.conf:/etc/nginx/nginx.conf:ro
depends_on:
- martin
martin:
2022-12-14 04:14:07 +03:00
image: maplibre/martin:v0.6.2
2021-10-07 17:00:21 +03:00
restart: unless-stopped
environment:
2022-12-22 09:35:29 +03:00
- DATABASE_URL=postgresql://postgresql:password@db/db
2021-10-07 17:00:21 +03:00
depends_on:
- db
db:
2022-12-04 08:34:44 +03:00
image: postgis/postgis:14-3.3-alpine
2021-10-07 17:00:21 +03:00
restart: unless-stopped
environment:
- POSTGRES_DB=db
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=password
volumes:
- ./pg_data:/var/lib/postgresql/data
```
2022-06-11 10:51:41 +03:00
You can find an example Nginx configuration file [here ](https://github.com/maplibre/martin/blob/main/nginx.conf ).
2021-10-07 17:00:21 +03:00
### Rewriting URLs
If you are running martin behind Nginx proxy, you may want to rewrite the request URL to properly handle tile URLs in [TileJSON ](#table-source-tilejson ) [endpoints ](#function-source-tilejson ).
2019-02-04 13:13:16 +03:00
```nginx
location ~ /tiles/(?< fwd_path > .*) {
2021-10-15 18:19:36 +03:00
proxy_set_header X-Rewrite-URL $uri;
2020-12-15 13:12:16 +03:00
proxy_set_header X-Forwarded-Host $host:$server_port;
2021-10-07 17:00:21 +03:00
proxy_set_header X-Forwarded-Proto $scheme;
proxy_redirect off;
2019-02-04 13:13:16 +03:00
proxy_pass http://martin:3000/$fwd_path$is_args$args;
}
```
2021-10-07 17:00:21 +03:00
### Caching tiles
You can also use Nginx to cache tiles. In the example, the maximum cache size is set to 10GB, and caching time is set to 1 hour for responses with codes 200, 204, and 302 and 1 minute for responses with code 404.
```nginx
http {
...
proxy_cache_path /var/cache/nginx/
levels=1:2
max_size=10g
use_temp_path=off
keys_zone=tiles_cache:10m;
server {
...
location ~ /tiles/(?< fwd_path > .*) {
2021-10-15 18:19:36 +03:00
proxy_set_header X-Rewrite-URL $uri;
2021-10-07 17:00:21 +03:00
proxy_set_header X-Forwarded-Host $host:$server_port;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_redirect off;
proxy_cache tiles_cache;
proxy_cache_lock on;
proxy_cache_revalidate on;
# Set caching time for responses
proxy_cache_valid 200 204 302 1h;
proxy_cache_valid 404 1m;
proxy_cache_use_stale error timeout http_500 http_502 http_503 http_504;
add_header X-Cache-Status $upstream_cache_status;
proxy_pass http://martin:3000/$fwd_path$is_args$args;
}
}
}
```
2022-06-11 10:51:41 +03:00
You can find an example Nginx configuration file [here ](https://github.com/maplibre/martin/blob/main/nginx.conf ).
2021-10-07 17:00:21 +03:00
2018-10-17 11:49:02 +03:00
## Building from Source
2018-10-10 16:32:24 +03:00
2018-10-17 11:49:02 +03:00
You can clone the repository and build martin using [cargo ](https://doc.rust-lang.org/cargo ) package manager.
2018-10-10 16:32:24 +03:00
```shell
2022-06-11 10:51:41 +03:00
git clone git@github.com:maplibre/martin.git
2018-10-10 16:32:24 +03:00
cd martin
cargo build --release
```
2018-10-17 11:49:02 +03:00
The binary will be available at `./target/release/martin` .
2018-10-01 20:10:33 +03:00
2018-10-10 16:32:24 +03:00
```shell
2018-10-10 19:27:21 +03:00
cd ./target/release/
2022-12-22 09:35:29 +03:00
./martin postgresql://postgres@localhost/db
2018-10-10 16:32:24 +03:00
```
2017-10-09 14:29:03 +03:00
2019-02-04 20:22:02 +03:00
## Debugging
Log levels are controlled on a per-module basis, and by default all logging is disabled except for errors. Logging is controlled via the `RUST_LOG` environment variable. The value of this environment variable is a comma-separated list of logging directives.
2021-07-16 13:09:18 +03:00
This will enable debug logging for all modules:
2019-02-04 20:22:02 +03:00
```shell
2021-07-16 13:09:18 +03:00
export RUST_LOG=debug
2022-12-22 09:35:29 +03:00
martin postgresql://postgres@localhost/db
2021-07-16 13:09:18 +03:00
```
While this will only enable verbose logging for the `actix_web` module and enable debug logging for the `martin` and `tokio_postgres` modules:
```shell
export RUST_LOG=actix_web=info,martin=debug,tokio_postgres=debug
2022-12-22 09:35:29 +03:00
martin postgresql://postgres@localhost/db
2019-02-04 20:22:02 +03:00
```
2017-10-09 14:29:03 +03:00
## Development
2022-10-28 21:52:39 +03:00
* Clone Martin
* Install [docker ](https://docs.docker.com/get-docker/ ), [docker-compose ](https://docs.docker.com/compose/ ), and [Just ](https://github.com/casey/just#readme ) (improved makefile processor)
* Run `just` to see available commands:
```shell, ignore
❯ git clone git@github.com:maplibre/martin.git
❯ cd martin
❯ just
2022-12-04 08:34:44 +03:00
Available recipes:
run *ARGS # Start Martin server and a test database
2022-12-10 10:40:01 +03:00
debug-page *ARGS # Start Martin server and open a test page
2022-12-04 08:34:44 +03:00
psql *ARGS # Run PSQL utility against the test database
clean # Perform cargo clean to delete all build files
clean-test # Delete test output files
start-db # Start a test database
start-legacy # Start a legacy test database
docker-up name # Start a specific test database, e.g. db or db-legacy
stop # Stop the test database
bench # Run benchmark tests
test # Run all tests using a test database
2022-12-12 18:16:23 +03:00
test-unit *ARGS # Run Rust unit and doc tests (cargo test)
2022-12-04 08:34:44 +03:00
test-int # Run integration tests
test-int-legacy # Run integration tests using legacy database
test-integration name # Run integration tests with the given docker compose target
docker-build # Build martin docker image
docker-run *ARGS # Build and run martin docker image
git *ARGS # Do any git command, ensuring that the testing environment is set up. Accepts the same arguments as git.
2022-12-10 10:40:01 +03:00
git-pre-push # These steps automatically run before git push via a git hook
2022-11-26 12:46:40 +03:00
```
2022-10-28 21:52:39 +03:00
### Other useful commands
2017-10-09 14:29:03 +03:00
2020-12-20 15:45:33 +03:00
```shell
2022-10-28 21:52:39 +03:00
# Start db service
2022-12-10 10:40:01 +03:00
just debug-page
2020-12-20 15:45:33 +03:00
2022-10-28 21:52:39 +03:00
# Run Martin server
2022-12-22 09:35:29 +03:00
DATABASE_URL=postgresql://postgres@localhost/db cargo run
2020-12-20 15:45:33 +03:00
```
2022-11-26 12:46:40 +03:00
Open `tests/debug.html` for debugging. By default, martin will be available at [localhost:3000 ](http://localhost:3000/ )
2020-12-20 15:45:33 +03:00
Make your changes, and check if all the tests are running
```shell
2022-12-22 09:35:29 +03:00
DATABASE_URL=postgresql://postgres@localhost/db cargo test
2018-10-17 11:49:02 +03:00
```
2021-07-23 11:04:34 +03:00
You can also run benchmarks with
```shell
2022-12-22 09:35:29 +03:00
DATABASE_URL=postgresql://postgres@localhost/db cargo bench
2021-07-23 11:04:34 +03:00
```
An HTML report displaying the results of the benchmark will be generated under `target/criterion/report/index.html`
2021-10-17 17:30:35 +03:00
## Recipes
2021-10-18 14:42:11 +03:00
### Using with DigitalOcean PostgreSQL
2021-10-21 12:20:33 +03:00
You can use martin with [Managed PostgreSQL from DigitalOcean ](https://www.digitalocean.com/products/managed-databases-postgresql/ ) with PostGIS extension
2021-10-18 14:42:11 +03:00
First, you need to download the CA certificate and get your cluster connection string from the [dashboard ](https://cloud.digitalocean.com/databases ). After that, you can use the connection string and the CA certificate to connect to the database
2022-08-11 14:06:12 +03:00
```shell
2022-12-22 09:35:29 +03:00
martin --ca-root-file ./ca-certificate.crt postgresql://user:password@host:port/db?sslmode=require
2021-10-18 14:42:11 +03:00
```
### Using with Heroku PostgreSQL
2021-10-17 17:30:35 +03:00
2021-10-21 12:20:33 +03:00
You can use martin with [Managed PostgreSQL from Heroku ](https://www.heroku.com/postgres ) with PostGIS extension
2021-10-17 17:30:35 +03:00
2022-08-11 14:06:12 +03:00
```shell
2021-10-17 17:30:35 +03:00
heroku pg:psql -a APP_NAME -c 'create extension postgis'
```
In order to trust the Heroku certificate, you can disable certificate validation with either `DANGER_ACCEPT_INVALID_CERTS` environment variable
2022-08-11 14:06:12 +03:00
```shell
2021-10-17 17:30:35 +03:00
DATABASE_URL=$(heroku config:get DATABASE_URL -a APP_NAME) DANGER_ACCEPT_INVALID_CERTS=true martin
```
or `--danger-accept-invalid-certs` command-line argument
2022-08-11 14:06:12 +03:00
```shell
2021-10-17 17:30:35 +03:00
martin --danger-accept-invalid-certs $(heroku config:get DATABASE_URL -a APP_NAME)
```