{"id":13426505,"url":"https://github.com/faradayio/tilesplash","last_synced_at":"2025-04-12T15:35:33.979Z","repository":{"id":18469627,"uuid":"21664546","full_name":"faradayio/tilesplash","owner":"faradayio","description":"a light and quick nodejs vector tile server for use with a postgis backend","archived":false,"fork":false,"pushed_at":"2018-09-04T19:47:03.000Z","size":103,"stargazers_count":178,"open_issues_count":17,"forks_count":37,"subscribers_count":20,"default_branch":"master","last_synced_at":"2025-04-02T10:43:42.807Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/faradayio.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2014-07-09T18:51:31.000Z","updated_at":"2024-11-21T09:13:58.000Z","dependencies_parsed_at":"2022-09-08T04:11:04.340Z","dependency_job_id":null,"html_url":"https://github.com/faradayio/tilesplash","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/faradayio%2Ftilesplash","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/faradayio%2Ftilesplash/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/faradayio%2Ftilesplash/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/faradayio%2Ftilesplash/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/faradayio","download_url":"https://codeload.github.com/faradayio/tilesplash/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248590131,"owners_count":21129754,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-07-31T00:01:36.471Z","updated_at":"2025-04-12T15:35:33.946Z","avatar_url":"https://github.com/faradayio.png","language":"JavaScript","funding_links":[],"categories":["JavaScript","Servers"],"sub_categories":[],"readme":"TILESPLASH\n==========\n\nA light and quick nodejs webserver for serving topojson and mapbox vector tiles from a [postgis](http://www.postgis.net/) backend. inspired by [Michal Migurski](http://mike.teczno.com/)'s [TileStache](http://tilestache.org/). Works great for powering [Mapbox-GL-based](https://www.mapbox.com/mapbox-gl-js/example/set-perspective/) apps like this:\n\n![example](https://www.dropbox.com/s/viwv9layui7vw7x/Screenshot%202016-10-14%2011.20.03.png?dl=1)\n\n# Dependencies\n\nTilesplash depends on `node` and `npm`\n\n# Installation\n\n```bash\nnpm install tilesplash\n```\n\n# Example\n\nHere's a simple tile server with one layer\n\n```javascript\nvar Tilesplash = require('tilesplash');\n\n// invoke tilesplash with DB options\nvar app = new Tilesplash({\n  user: myUser,\n  password: myPassword,\n  host: localhost,\n  port: 5432,\n  database: myDb\n});\n\n// define a layer\napp.layer('test_layer', function(tile, render){\n  render('SELECT ST_AsGeoJSON(the_geom) as the_geom_geojson FROM layer WHERE ST_Intersects(the_geom, !bbox_4326!)');\n});\n\n// serve tiles at port 3000\napp.server.listen(3000);\n```\n\n- Topojson tiles will be available at `http://localhost:3000/test_layer/{z}/{x}/{y}.topojson`\n- Mapbox vector tiles will be available at `http://localhost:3000/test_layer/{z}/{x}/{y}.mvt`\n\n(See [client implementation examples](https://github.com/faradayio/tilesplash#client) below, and complete demo implementation in [`demo/`](demo/README.md))\n\n# Usage\n\n## `new Tilesplash(connection_details, [cacheType])`\n\ncreates a new tilesplash server using the given postgres database\n\n```javascript\nvar dbConfig = {\n  user: username,\n  password: password,\n  host: hostname,\n  port: 5432,\n  database: dbname\n}\n\nvar app = new Tilesplash(dbConfig);\n```\n\nTo cache using redis, pass `'redis'` as the second argument. Otherwise an in-process cache will be used.\n\n### `Tilesplash.server`\n\nan [express](http://expressjs.com/) object, mostly used internally but you can use it to add middleware for authentication, browser caching, gzip, etc.\n\n### `Tilesplash.layer(name, [middleware, ...], [mvtOptions], callback)`\n\n__name__: the name of your layer. Tiles will be served at /__name__/z/x/y.topojson\n\n__middleware__: a [middleware function](#middleware)\n\n__mvtOptions__: optional [mapnik parameters](http://mapnik.org/documentation/node-mapnik/3.5/#VectorTile.addGeoJSON), e.g. `{ strictly_simple: true }`\n\n__callback__: your tile building function with the following arguments. function([tile](#tile), [render](#render))\n\n\n#### Simple layer\n\nThis layer renders tiles containing geometry from the `the_geom` column in `test_table`\n\n```javascript\napp.layer('simpleLayer', function(tile, render){\n  render('SELECT ST_AsGeoJSON(the_geom) as the_geom_geojson FROM test_table WHERE ST_Intersects(the_geom, !bbox_4326!)');\n});\n```\n\n#### Combined layers\n\nTilesplash can render tiles from multiple queries at once\n\n```javascript\napp.layer('multiLayer', function(tile, render){\n  render({\n    circles: 'SELECT ST_AsGeoJSON(the_geom) as the_geom_geojson FROM circles WHERE ST_Intersects(the_geom, !bbox_4326!)',\n    squares: 'SELECT ST_AsGeoJSON(the_geom) as the_geom_geojson FROM squares WHERE ST_Intersects(the_geom, !bbox_4326!)'\n  });\n});\n```\n\nIn fact, `simpleLayer` is just a shorthand for `multiLayer` with the only query named `vectile`.\nThe code below is equivalent to the _simple layer example_:\n\n```javascript\napp.layer('multiLayer', function(tile, render){\n  render({\n    vectile: 'SELECT ST_AsGeoJSON(the_geom) as the_geom_geojson FROM test_table WHERE ST_Intersects(the_geom, !bbox_4326!)',\n  });\n});\n```\n\nKnowing the name of the query (i.e. `vectile`, `circles`, `squares`, etc.) is important when rendering tiles on the client.\nThe value corresponds to `source-layer` in [Mapbox GL JS](https://www.mapbox.com/mapbox-gl-js/api/) layer spec (see [example](https://www.mapbox.com/mapbox-gl-js/example/vector-source/)).\n\n\n#### Using mapnik geometry parameters\n\nThis layer renders tiles containing geometry features simplified to a threshold of `4`. Full parameters are documented [here](http://mapnik.org/documentation/node-mapnik/3.5/#VectorTile.addGeoJSON).\n\n```javascript\napp.layer('simpleLayer', { simplify_distance: 4 }, function(tile, render){\n  render('SELECT ST_AsGeoJSON(the_geom) as the_geom_geojson FROM test_table WHERE ST_Intersects(the_geom, !bbox_4326!)');\n});\n```\n\n#### Escaping variables\n\nTilesplash 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.\n\n```javascript\napp.layer('escapedLayer', function(tile, render){\n  render(['SELECT ST_AsGeoJSON(the_geom) as the_geom_geojson FROM points WHERE ST_Intersects(the_geom, !bbox_4326!) AND state=$1', 'California']);\n});\n\napp.layer('escapedMultiLayer', function(tile, render){\n  render({\n    hotels: ['SELECT ST_AsGeoJSON(the_geom) as the_geom_geojson FROM hotels WHERE ST_Intersects(the_geom, !bbox_4326!) AND state=$1', 'California'],\n    restaurants: ['SELECT ST_AsGeoJSON(the_geom) as the_geom_geojson FROM restaurants WHERE ST_Intersects(the_geom, !bbox_4326!) AND state=$1', 'California']\n  });\n});\n```\n\n#### Restricting zoom level\n\nSometimes 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.\n\n```javascript\napp.layer('zoomDependentLayer', function(tile, render){\n  if (tile.z \u003c 8 || tile.z \u003e 20) {\n    render.empty(); //render an empty tile\n  } else {\n    render('SELECT ST_AsGeoJSON(the_geom) as the_geom_geojson FROM points WHERE ST_Intersects(the_geom, !bbox_4326!)');\n  }\n});\n```\n\nYou can also adapt your layer by zoom level to show different views in different situations.\n\nIn 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.\n\n```javascript\napp.layer('fancyLayer', function(tile, render){\n  if (tile.z \u003c 8) {\n    render('SELECT ST_AsGeoJSON(the_geom) as the_geom_geojson FROM heatmap WHERE ST_Intersects(the_geom, !bbox_4326!)');\n  } else if (tile.z \u003e 20) {\n    render.empty();\n  } else {\n    render('SELECT ST_AsGeoJSON(the_geom) as the_geom_geojson FROM points WHERE ST_Intersects(the_geom, !bbox_4326!)');\n  }\n});\n```\n\n## Middleware\n\nMiddleware allows you to easily extend tilesplash to add additional functionality. Middleware is defined like this:\n\n```javascript\nvar userMiddleware = function(req, res, tile, next){\n  tile.logged_in = true;\n  tile.user_id = req.query.user_id;\n  next();\n};\n```\n\nYou can layer include this in your layers\n\n```javascript\napp.layer('thisOneHasMiddleware', userMiddleware, function(tile, render){\n  if (!tile.logged_in) {\n    render.error();\n  } else {\n    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]);\n  }\n});\n```\n\nMiddleware can be synchronous or asynchronous, just be sure to call `next()` when you're done!\n\n## tile\n\n`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:\n\n```javascript\n{\n  x: 100,\n  y: 100,\n  z: 10,\n  bounds: [w, s, e, n] //output from SphericalMercator.bounds(x,y,z) using https://github.com/mapbox/node-sphericalmercator\n  bbox: 'BBOX SQL for webmercator',\n  bbox_4326: 'BBOX SQL for 4326 projection' //you probably need this\n}\n```\n\nAnything in __tile__ can be substituted into your SQL query by wrapping it in exclamation marks like `!this!`\n\nYou can add custom items into __tile__ like so:\n\n```javascript\ntile.table = \"states\";\nrender('SELECT ST_AsGeoJSON(the_geom) as the_geom_geojson FROM !table! WHERE !bbox!')\n```\n\nNote 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.\n\nWhen you want to use user input in a query, see [Escaping variables](#escaping-variables) above.\n\n## `render`\n\n`render` is the second argument passed to your layer callback function. You can use it to render different kinds of tiles.\n\n### `render(sql)`\n\nRuns a SQL query and displays the result as a tile\n\n### `render(object)`\n\nRuns multiple SQL queries and renders them in seperate topojson layers. See [Combined layers](#combined-layers) above.\n\n### `render.query()`\n\nAlias of render()\n\n### `render.queryFile(fileName)`\n\nUse this if your SQL is really long and/or you want to keep it seperate.\n\n```javascript\napp.layer('complicatedLayer', function(tile, render){\n  render.queryFile('important_stuff/advanced_tile.sql');\n});\n```\n\n### `render.empty()`\n\nRenders an empty tile\n\n### `render.error()`\n\nReplies with a 500 error\n\n### `render.raw(string or http code)`\n\nSends a raw reply. I can't think of any reason you would want to do this, but feel free to experiment.\n\n```javascript\napp.layer('smileyLayer', function(tile, render){\n  render.raw(':)');\n});\n```\n\n```javascript\napp.layer('notThereLayer', function(tile, render){\n  render.raw(404);\n});\n```\n\n### `render.rawFile(fileName)`\n\nReplies with the specified file\n\n```javascript\napp.layer('staticLayer', function(tile, render){\n  render.rawFile('thing.topojson');\n});\n```\n\n## Caching\n\nCaching 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.\n\nThere are two ways to implement caching. You can either do it globally or on a layer by layer basis.\n\n### app.cache([keyGenerator], ttl)\n\nUse this to define caching across your entire application\n\n__`keyGenerator(tile)`__\n\nkeyGenerator is a function that takes a `tile` object as it's only parameter and returns a cache key (__string__)\n\nIf 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.\n\n__`ttl`__\n\nTTL 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.\n\n__Example__\n\nIn 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.\n\n```javascript\napp.cache(function(tile){\n  return app.defaultCacheKeyGenerator(tile) + ':' + tile.user_id; //cache by tile.user_id as well\n}, 1000 * 60 * 60 * 24 * 30); //ttl 30 days\n```\n\n### `this.cache([keyGenerator], ttl)`\n\nLayer-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.\n\nIn this example, slowLayer uses the same key generator as the rest of the app, but specifies a longer TTL.\n\n```javascript\napp.cache(keyGenerator, 1000 * 60 * 60 * 24); //cache for one day\n\napp.layer('slowLayer', function(tile, render){\n  this.cache(1000 * 60 * 60 * 24 * 30); //cache for 30 days\n\n  render.queryFile('slowQuery.sql');\n});\n```\n\nIn this example, only slowLayer is cached.\n\n```javascript\napp.layer('fastLayer', function(tile, render){\n  render.queryFile('fastQuery.sql');\n});\n\nvar userMiddleware = function(req, res, tile, next){\n  tile.user_id = 1;\n  next();\n};\n\napp.layer('slowLayer', userMiddleware, function(tile, render){\n  this.cache(function(tile){\n    return app.defaultCacheKeyGenerator(tile) + ':' + tile.user_id;\n  }, 1000 * 60 * 60 * 24); //cache for one day\n\n  render.queryFile('slowQuery.sql');\n});\n```\n\n## Client\nSome in-browser examples of how to use the tiles generated by tilesplash:\n\n### .mvt endpoint\n- [Mapbox GL third-party source example](https://www.mapbox.com/mapbox-gl-js/example/third-party/)\n- [Mapzen's Tangram](https://github.com/tangrams/tangram)\n- [Mapzen's d3 vector tiles implementation](http://mapzen.github.io/d3-vector-tiles)\n \n### .topojson endpoint\n- [D3 + leaflet](http://bl.ocks.org/wboykinm/7393674)\n- [Mapzen Tangram](https://github.com/tangrams/tangram)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffaradayio%2Ftilesplash","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffaradayio%2Ftilesplash","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffaradayio%2Ftilesplash/lists"}