• Stars
    star
    176
  • Rank 216,987 (Top 5 %)
  • Language
    JavaScript
  • License
    MIT License
  • Created over 10 years ago
  • Updated about 6 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

a light and quick nodejs vector tile server for use with a postgis backend

TILESPLASH

A light and quick nodejs webserver for serving topojson and mapbox vector tiles from a postgis backend. inspired by Michal Migurski's TileStache. Works great for powering Mapbox-GL-based apps like this:

example

Dependencies

Tilesplash depends on node and npm

Installation

npm install tilesplash

Example

Here's a simple tile server with one layer

var Tilesplash = require('tilesplash');

// invoke tilesplash with DB options
var app = new Tilesplash({
  user: myUser,
  password: myPassword,
  host: localhost,
  port: 5432,
  database: myDb
});

// define a layer
app.layer('test_layer', function(tile, render){
  render('SELECT ST_AsGeoJSON(the_geom) as the_geom_geojson FROM layer WHERE ST_Intersects(the_geom, !bbox_4326!)');
});

// serve tiles at port 3000
app.server.listen(3000);
  • Topojson tiles will be available at http://localhost:3000/test_layer/{z}/{x}/{y}.topojson
  • Mapbox vector tiles will be available at http://localhost:3000/test_layer/{z}/{x}/{y}.mvt

(See client implementation examples below, and complete demo implementation in demo/)

Usage

new Tilesplash(connection_details, [cacheType])

creates a new tilesplash server using the given postgres database

var dbConfig = {
  user: username,
  password: password,
  host: hostname,
  port: 5432,
  database: dbname
}

var app = new Tilesplash(dbConfig);

To cache using redis, pass 'redis' as the second argument. Otherwise an in-process cache will be used.

Tilesplash.server

an express object, mostly used internally but you can use it to add middleware for authentication, browser caching, gzip, etc.

Tilesplash.layer(name, [middleware, ...], [mvtOptions], callback)

name: the name of your layer. Tiles will be served at /name/z/x/y.topojson

middleware: a middleware function

mvtOptions: optional mapnik parameters, e.g. { strictly_simple: true }

callback: your tile building function with the following arguments. function(tile, render)

Simple layer

This layer renders tiles containing geometry from the the_geom column in test_table

app.layer('simpleLayer', function(tile, render){
  render('SELECT ST_AsGeoJSON(the_geom) as the_geom_geojson FROM test_table WHERE ST_Intersects(the_geom, !bbox_4326!)');
});

Combined layers

Tilesplash can render tiles from multiple queries at once

app.layer('multiLayer', function(tile, render){
  render({
    circles: 'SELECT ST_AsGeoJSON(the_geom) as the_geom_geojson FROM circles WHERE ST_Intersects(the_geom, !bbox_4326!)',
    squares: 'SELECT ST_AsGeoJSON(the_geom) as the_geom_geojson FROM squares WHERE ST_Intersects(the_geom, !bbox_4326!)'
  });
});

In fact, simpleLayer is just a shorthand for multiLayer with the only query named vectile. The code below is equivalent to the simple layer example:

app.layer('multiLayer', function(tile, render){
  render({
    vectile: 'SELECT ST_AsGeoJSON(the_geom) as the_geom_geojson FROM test_table WHERE ST_Intersects(the_geom, !bbox_4326!)',
  });
});

Knowing the name of the query (i.e. vectile, circles, squares, etc.) is important when rendering tiles on the client. The value corresponds to source-layer in Mapbox GL JS layer spec (see example).

Using mapnik geometry parameters

This layer renders tiles containing geometry features simplified to a threshold of 4. Full parameters are documented here.

app.layer('simpleLayer', { simplify_distance: 4 }, function(tile, render){
  render('SELECT ST_AsGeoJSON(the_geom) as the_geom_geojson FROM test_table WHERE ST_Intersects(the_geom, !bbox_4326!)');
});

Escaping variables

Tilesplash has support for escaping variables in sql queries. You can do so by passing an array instead of a string wherever a sql string is accepted.

app.layer('escapedLayer', function(tile, render){
  render(['SELECT ST_AsGeoJSON(the_geom) as the_geom_geojson FROM points WHERE ST_Intersects(the_geom, !bbox_4326!) AND state=$1', 'California']);
});

app.layer('escapedMultiLayer', function(tile, render){
  render({
    hotels: ['SELECT ST_AsGeoJSON(the_geom) as the_geom_geojson FROM hotels WHERE ST_Intersects(the_geom, !bbox_4326!) AND state=$1', 'California'],
    restaurants: ['SELECT ST_AsGeoJSON(the_geom) as the_geom_geojson FROM restaurants WHERE ST_Intersects(the_geom, !bbox_4326!) AND state=$1', 'California']
  });
});

Restricting zoom level

Sometimes you only want a layer to be visible on certain zoom levels. To do that, we simply render an empty tile when tile.z is too low or too high.

app.layer('zoomDependentLayer', function(tile, render){
  if (tile.z < 8 || tile.z > 20) {
    render.empty(); //render an empty tile
  } else {
    render('SELECT ST_AsGeoJSON(the_geom) as the_geom_geojson FROM points WHERE ST_Intersects(the_geom, !bbox_4326!)');
  }
});

You can also adapt your layer by zoom level to show different views in different situations.

In this example we show data from the heatmap table when the zoom level is below 8, data from points up to zoom 20, and empty tiles when you zoom in further than that.

app.layer('fancyLayer', function(tile, render){
  if (tile.z < 8) {
    render('SELECT ST_AsGeoJSON(the_geom) as the_geom_geojson FROM heatmap WHERE ST_Intersects(the_geom, !bbox_4326!)');
  } else if (tile.z > 20) {
    render.empty();
  } else {
    render('SELECT ST_AsGeoJSON(the_geom) as the_geom_geojson FROM points WHERE ST_Intersects(the_geom, !bbox_4326!)');
  }
});

Middleware

Middleware allows you to easily extend tilesplash to add additional functionality. Middleware is defined like this:

var userMiddleware = function(req, res, tile, next){
  tile.logged_in = true;
  tile.user_id = req.query.user_id;
  next();
};

You can layer include this in your layers

app.layer('thisOneHasMiddleware', userMiddleware, function(tile, render){
  if (!tile.logged_in) {
    render.error();
  } else {
    render(['SELECT ST_AsGeoJSON(the_geom) as the_geom_geojson FROM placesVisited WHERE ST_Intersects(the_geom, !bbox_4326!) AND visitor=$1', tile.user_id]);
  }
});

Middleware can be synchronous or asynchronous, just be sure to call next() when you're done!

tile

tile is a parameter passed to middleware and layer callbacks. It is an object containing information about the tile being requested. It will look something like this:

{
  x: 100,
  y: 100,
  z: 10,
  bounds: [w, s, e, n] //output from SphericalMercator.bounds(x,y,z) using https://github.com/mapbox/node-sphericalmercator
  bbox: 'BBOX SQL for webmercator',
  bbox_4326: 'BBOX SQL for 4326 projection' //you probably need this
}

Anything in tile can be substituted into your SQL query by wrapping it in exclamation marks like !this!

You can add custom items into tile like so:

tile.table = "states";
render('SELECT ST_AsGeoJSON(the_geom) as the_geom_geojson FROM !table! WHERE !bbox!')

Note that when you interpolate tile variables into your queries with the exclamation point syntax, that data will not be escaped. This allows you to insert custom SQL from tile variables, like with !bbox!, but it can be a security risk if you allow any user input to be interpolated that way.

When you want to use user input in a query, see Escaping variables above.

render

render is the second argument passed to your layer callback function. You can use it to render different kinds of tiles.

render(sql)

Runs a SQL query and displays the result as a tile

render(object)

Runs multiple SQL queries and renders them in seperate topojson layers. See Combined layers above.

render.query()

Alias of render()

render.queryFile(fileName)

Use this if your SQL is really long and/or you want to keep it seperate.

app.layer('complicatedLayer', function(tile, render){
  render.queryFile('important_stuff/advanced_tile.sql');
});

render.empty()

Renders an empty tile

render.error()

Replies with a 500 error

render.raw(string or http code)

Sends a raw reply. I can't think of any reason you would want to do this, but feel free to experiment.

app.layer('smileyLayer', function(tile, render){
  render.raw(':)');
});
app.layer('notThereLayer', function(tile, render){
  render.raw(404);
});

render.rawFile(fileName)

Replies with the specified file

app.layer('staticLayer', function(tile, render){
  render.rawFile('thing.topojson');
});

Caching

Caching is very important. By default, Tilesplash uses an in-memory cache. You can use redis instead by passing 'redis' as the second argument when initializing a Tilesplash server.

There are two ways to implement caching. You can either do it globally or on a layer by layer basis.

app.cache([keyGenerator], ttl)

Use this to define caching across your entire application

keyGenerator(tile)

keyGenerator is a function that takes a tile object as it's only parameter and returns a cache key (string)

If you don't specify a key generator, app.defaultCacheKeyGenerator will be used, which returns a key derived from your database connection, tile layer, and tile x, y, and z.

ttl

TTL stands for time-to-live. It's how long tiles will remain in your cache, and it's defined in milliseconds. For most applications, anywhere between one day (86400000) to one week (604800000) should be fine.

Example

In this example, we have tile.user_id available to us and we don't want to show one user tiles belonging to another user. By starting with app.defaultCacheKeyGenerator(tile) we get a cache key based on things we already want to cache by (like x, y, and z) and we can then add user_id to prevent people from seeing cached tiles unless their user_id matches.

app.cache(function(tile){
  return app.defaultCacheKeyGenerator(tile) + ':' + tile.user_id; //cache by tile.user_id as well
}, 1000 * 60 * 60 * 24 * 30); //ttl 30 days

this.cache([keyGenerator], ttl)

Layer-specific caching works identically to global caching as defined above, except that it only applies to one layer and you define it within that layer.

In this example, slowLayer uses the same key generator as the rest of the app, but specifies a longer TTL.

app.cache(keyGenerator, 1000 * 60 * 60 * 24); //cache for one day

app.layer('slowLayer', function(tile, render){
  this.cache(1000 * 60 * 60 * 24 * 30); //cache for 30 days

  render.queryFile('slowQuery.sql');
});

In this example, only slowLayer is cached.

app.layer('fastLayer', function(tile, render){
  render.queryFile('fastQuery.sql');
});

var userMiddleware = function(req, res, tile, next){
  tile.user_id = 1;
  next();
};

app.layer('slowLayer', userMiddleware, function(tile, render){
  this.cache(function(tile){
    return app.defaultCacheKeyGenerator(tile) + ':' + tile.user_id;
  }, 1000 * 60 * 60 * 24); //cache for one day

  render.queryFile('slowQuery.sql');
});

Client

Some in-browser examples of how to use the tiles generated by tilesplash:

.mvt endpoint

.topojson endpoint

More Repositories

1

cage

Develop and deploy complex Docker applications
Rust
303
star
2

hangar

Use Rails/FactoryGirl factories from your frontend tests
Ruby
56
star
3

scrubcsv

Remove bad records from a CSV file and normalize
Rust
55
star
4

earth

Land, sky, and sea
Ruby
41
star
5

credentials_to_env

Downloads credentials from Hashicorp's Vault, and writes them to env vars and/or files before executing another process.
Rust
38
star
6

devise-auth0

Authenticate auth0 users in ruby web apps
Ruby
31
star
7

utility-landscape

A repository for creating and maintaining a geospatial representation of U.S. electrical utilities' service territories
Shell
19
star
8

exec-rs

Rust wrapper around the C execvp function, which replaces the current process with the specified program and arguments.
Rust
13
star
9

tronprint

Estimate your Ruby application's carbon footprint
Ruby
10
star
10

vault-env-js

Put your vault secrets in your process.env
TypeScript
10
star
11

catcsv

Concatenate CSV files, directories of CSV files, and snappy-compressed CSV files.
Rust
9
star
12

selectstar

Generate safe SQL statements with tagged literals
TypeScript
9
star
13

argo

Rapid reverse-geocoding using Mapzen Search
JavaScript
8
star
14

carbon-old

Gem implementing Brighter Planet's emissions estimates API
Ruby
7
star
15

falconeri

Transform lots of data using a Kubernetes cluster
Rust
7
star
16

node_smartystreets

DEPRECATED - use https://github.com/faradayio/geocode-csv. Was: fast SmartyStreets geocoder
TypeScript
7
star
17

carbon

Brighter Planet CM1 client for Ruby
Ruby
7
star
18

api

Documentation and resources for the Faraday API
6
star
19

geocode-csv

Geocode a CSV file using the SmartyStreets API
Rust
6
star
20

add-vault-tokens

Given a docker-compose.yml file and master token for vault, generate individual, restricted tokens for each app in the docker-compose.yml file.
Ruby
6
star
21

csvkiller

Segment CSV files by any column
JavaScript
6
star
22

emitter

The umbrella library for all Brighter Planet environmental impact models.
Ruby
6
star
23

csv-tools

Rust tools for working with CSV files: scrubcsv, catcsv, fixed2csv, geochunk, hashcsv.
Jupyter Notebook
4
star
24

geochunk

Group zip codes into "geochunks" with similar-sized populations. Run on CSV in a pipeline to add a column, or export the geochunk table.
Jupyter Notebook
4
star
25

brighter_planet_layout

Layout assets for Brighter Planet sites
Ruby
4
star
26

automobile

A carbon model
Ruby
4
star
27

ecs_compose

WORK IN PROGRESS: docker-compose.yml-based interface for Amazon EC2 Container Service (ECS)
Ruby
4
star
28

hootroot1

Hootroot: give a hoot, green your route
JavaScript
4
star
29

hangarjs

Hangar's other half
JavaScript
4
star
30

Leaflet.CanvasMask

Mask geojson stuff with canvas and stuff
JavaScript
3
star
31

barbq

Python-native SQL dialect that renders to BigQuery
Python
3
star
32

computation

A carbon model.
Ruby
3
star
33

flight

A carbon model
Ruby
3
star
34

remote-task

Simple server for remotely running tasks and logging their output to papertrail
JavaScript
3
star
35

charisma

Define attribute curation and display strategies for your rich Ruby classes
Ruby
3
star
36

greendreams

APIs for the environment
JavaScript
3
star
37

brighter_planet_metadata

Names of resources, emitters, etc. provided by Brighter Planet.
Ruby
3
star
38

censusapi2csv

A tool for downloading Census/ACS data from the API and parsing to CSV
JavaScript
3
star
39

angular-liveaddress

Use SmartyStreets to autocomplete address fields
JavaScript
3
star
40

electricity_use

Brighter Planet CM1 Carbon model for general electricity use
Ruby
3
star
41

gistkiller

Clean up your gists, hide those skeletons
Ruby
2
star
42

journaling-hash

Immutable hash that can back itself up to a file and rebuild itself
JavaScript
2
star
43

places

Faraday's gazetter: 78,000 of the best places in the U.S. (all of them)
2
star
44

energy

Energy use information client
Ruby
2
star
45

bigml-rs

A Rust client library for the BigML machine learning service (WIP).
Rust
2
star
46

greenbac

A Carbon Middleware demo
JavaScript
2
star
47

through2-linereader

Yet another line reader stream, uses through2
JavaScript
2
star
48

plancha

A node CLI tool to flatten multi-sheet/tab spreadsheet files
JavaScript
2
star
49

RepAssignment

rep assignment problems with or-tools
Python
2
star
50

careplane

Careplane is a browser plugin that displays carbon footprints on top of flight search results
JavaScript
2
star
51

sniff

Testing for emissions
Ruby
2
star
52

lock_and_cache_js

Most caching libraries don't do locking, meaning that >1 process can be calculating a cached value at the same time. Since you presumably cache things because they cost CPU, database reads, or money, doesn't it make sense to lock while caching?
TypeScript
2
star
53

numbers

Safety in Numbers, the legacy blog of Brighter Planet
CSS
2
star
54

lodging

A carbon model
Ruby
2
star
55

d3-topojson-layer

Topojson tiles in leaflet with d3
JavaScript
2
star
56

yaktrak

Fedex package tracking—now with CO₂!
Ruby
1
star
57

mapsquare

Get GeoJSON from the Foursquare venues API
JavaScript
1
star
58

hagar1

Development environment for our Rails 2 apps: data1, wlpf1, etc.
Ruby
1
star
59

ubuntu_dbcrossbar

A docker container with dbcrossbar and related utils.
Dockerfile
1
star
60

numbers-staging

Staging environment for the blog
Ruby
1
star
61

fedprint

Carbon footprints of federal contracts
Ruby
1
star
62

bierstadt

Automated print cartography with node.js and d3.js
JavaScript
1
star
63

openapi-interfaces

Extend OpenAPI with support for interfaces and automatic generation of related types like MergePatch. Compile to plain OpenAPI.
Rust
1
star
64

fixed2csv

Converts fixed-width fields to valid CSV data.
Rust
1
star
65

automobile_trip

A carbon model
Ruby
1
star
66

shipment

A carbon model for a shipment
Ruby
1
star
67

kayak1

A Carbon Middleware demo
Ruby
1
star
68

fdy-dendrite

A command-line tool to disaggregate rows in a csv
JavaScript
1
star
69

parsers

A home for parsers used by http://data.brighterplanet.com
1
star
70

aftermath-sites

Aftermath demo
1
star
71

manually_curated_data

A home for lookup tables, dictionaries and errata used by http://data.brighterplanet.com. Name subject to change.
1
star
72

pachyderm_large_file_test

Code to test Pachyderm's large file support
Shell
1
star
73

yardcarbon

Assets for inserting carbon footprints into the EY cloud dashboard
JavaScript
1
star
74

zillow1

A Carbon Middleware demo
Ruby
1
star
75

ci1

Continuous Integration
Ruby
1
star
76

clausfreight

Claus Freight Co. homepage
JavaScript
1
star
77

facility

Ruby
1
star
78

deploy

Brighter Planet's deploy system, published as the brighter_planet_deploy gem
Ruby
1
star
79

consignment

An emissions model
Ruby
1
star
80

ey-cloud-recipes-old1

A starter repo for custom chef recipes on EY's cloud platform
Ruby
1
star
81

cm1-route

Routing/footprint engine extracted from HootRoot
JavaScript
1
star