├── .dockerignore ├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── app.js ├── config.js ├── data ├── world_merc.dbf ├── world_merc.json ├── world_merc.prj ├── world_merc.shp ├── world_merc.shx └── world_merc_license.txt ├── package.json ├── utils ├── pool.js ├── sphericalmercator.js └── tile.js ├── vector_tile.js └── xml └── point_vector.xml /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | data 3 | .git 4 | xml -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .vscode 3 | data 4 | xml -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:v8.11.3-ubuntu 2 | 3 | WORKDIR /var/lib/vector-tile-server 4 | 5 | COPY ./ ./ 6 | RUN cnpm install 7 | 8 | EXPOSE 8002 9 | 10 | CMD [ "node", "app.js" ] 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 qingyafan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PG-TILER 2 | 3 | 初衷是一个渲染PostgreSQL/PostGIS中存储地理数据的易安装的应用程序,它应当的职责是从数据库中取数据,然后转换成客户端(Web、手机端)需要的格式(栅格切片、矢量切片),并通过REST API返回。 4 | 5 | ## 使用说明 6 | 7 | 目前只支持输出EPSG:3857坐标系下的矢量瓦片 8 | 9 | ## 如何使用 10 | 11 | 测试都是使用 node v57 12 | 13 | 1. Clone the project, 14 | 2. run `npm install`, then `node app.js`, 15 | 3. now you can access your tile from `/vt/tile/:z/:x:y?table=&geometry_field=`. -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | const Koa = require("koa"); 2 | const Router = require("koa-router"); 3 | const BodyParser = require("koa-bodyparser"); 4 | const app = new Koa(); 5 | const router = new Router(); 6 | const config = require("./config"); 7 | const vectorTile = require("./vector_tile"); 8 | const cors = require("@koa/cors"); 9 | 10 | app.use(cors()); 11 | app.use(router.routes()); 12 | app.use(BodyParser()); 13 | 14 | router.get(`/vt/status`, (ctx)=> { 15 | ctx.body = { 16 | status: "running" 17 | } 18 | }) 19 | 20 | router.get(`/vt/tile/:z/:x/:y`, vectorTile.tile) 21 | 22 | app.listen(config.port, () => { 23 | console.log(`Mapnik Vector Tile Server Running At: ${config.port}`) 24 | }) -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | port: 8002, 3 | db: { 4 | host: 'postgres', 5 | port: 5432, 6 | user: '', 7 | password: '', 8 | database: '' 9 | }, 10 | cache: { 11 | host: 'redis', 12 | port: 6379 13 | } 14 | } -------------------------------------------------------------------------------- /data/world_merc.dbf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QingyaFan/pg-tiler/f364c05fce916e015e180079264dad5adaf7ec2c/data/world_merc.dbf -------------------------------------------------------------------------------- /data/world_merc.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QingyaFan/pg-tiler/f364c05fce916e015e180079264dad5adaf7ec2c/data/world_merc.json -------------------------------------------------------------------------------- /data/world_merc.prj: -------------------------------------------------------------------------------- 1 | PROJCS["Google Maps Global Mercator",GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]],PROJECTION["Mercator_2SP"],PARAMETER["standard_parallel_1",0],PARAMETER["latitude_of_origin",0],PARAMETER["central_meridian",0],PARAMETER["false_easting",0],PARAMETER["false_northing",0],UNIT["Meter",1]] -------------------------------------------------------------------------------- /data/world_merc.shp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QingyaFan/pg-tiler/f364c05fce916e015e180079264dad5adaf7ec2c/data/world_merc.shp -------------------------------------------------------------------------------- /data/world_merc.shx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QingyaFan/pg-tiler/f364c05fce916e015e180079264dad5adaf7ec2c/data/world_merc.shx -------------------------------------------------------------------------------- /data/world_merc_license.txt: -------------------------------------------------------------------------------- 1 | world_merc 2 | ========== 3 | 4 | 'world_merc.shp' is a version of TM_WORLD_BORDERS_SIMPL-0.3.shp 5 | downloaded from http://thematicmapping.org/downloads/world_borders.php. 6 | 7 | Coodinates near 180 degress longitude were clipped to faciliate reprojection 8 | to Google mercator (EPSG:900913). 9 | 10 | Details from original readme are below: 11 | 12 | ------------- 13 | 14 | TM_WORLD_BORDERS-0.1.ZIP 15 | 16 | Provided by Bjorn Sandvik, thematicmapping.org 17 | 18 | Use this dataset with care, as several of the borders are disputed. 19 | 20 | The original shapefile (world_borders.zip, 3.2 MB) was downloaded from the Mapping Hacks website: 21 | http://www.mappinghacks.com/data/ 22 | 23 | The dataset was derived by Schuyler Erle from public domain sources. 24 | Sean Gilles did some clean up and made some enhancements. 25 | 26 | 27 | COLUMN TYPE DESCRIPTION 28 | 29 | Shape Polygon Country/area border as polygon(s) 30 | FIPS String(2) FIPS 10-4 Country Code 31 | ISO2 String(2) ISO 3166-1 Alpha-2 Country Code 32 | ISO3 String(3) ISO 3166-1 Alpha-3 Country Code 33 | UN Short Integer(3) ISO 3166-1 Numeric-3 Country Code 34 | NAME String(50) Name of country/area 35 | AREA Long Integer(7) Land area, FAO Statistics (2002) 36 | POP2005 Double(10,0) Population, World Polulation Prospects (2005) 37 | REGION Short Integer(3) Macro geographical (continental region), UN Statistics 38 | SUBREGION Short Integer(3) Geogrpahical sub-region, UN Statistics 39 | LON FLOAT (7,3) Longitude 40 | LAT FLOAT (6,3) Latitude 41 | 42 | 43 | CHANGELOG VERSION 0.3 - 30 July 2008 44 | 45 | - Corrected spelling mistake (United Arab Emirates) 46 | - Corrected population number for Japan 47 | - Adjusted long/lat values for India, Italy and United Kingdom 48 | 49 | 50 | CHANGELOG VERSION 0.2 - 1 April 2008 51 | 52 | - Made new ZIP archieves. No change in dataset. 53 | 54 | 55 | CHANGELOG VERSION 0.1 - 13 March 2008 56 | 57 | - Polygons representing each country were merged into one feature 58 | - ≈land Islands was extracted from Finland 59 | - Hong Kong was extracted from China 60 | - Holy See (Vatican City) was added 61 | - Gaza Strip and West Bank was merged into "Occupied Palestinean Territory" 62 | - Saint-Barthelemy was extracted from Netherlands Antilles 63 | - Saint-Martin (Frensh part) was extracted from Guadeloupe 64 | - Svalbard and Jan Mayen was merged into "Svalbard and Jan Mayen Islands" 65 | - Timor-Leste was extracted from Indonesia 66 | - Juan De Nova Island was merged with "French Southern & Antarctic Land" 67 | - Baker Island, Howland Island, Jarvis Island, Johnston Atoll, Midway Islands 68 | and Wake Island was merged into "United States Minor Outlying Islands" 69 | - Glorioso Islands, Parcel Islands, Spartly Islands was removed 70 | (almost uninhabited and missing ISO-3611-1 code) 71 | 72 | - Added ISO-3166-1 codes (alpha-2, alpha-3, numeric-3). Source: 73 | https://www.cia.gov/library/publications/the-world-factbook/appendix/appendix-d.html 74 | http://unstats.un.org/unsd/methods/m49/m49alpha.htm 75 | http://www.fysh.org/~katie/development/geography.txt 76 | - AREA column has been replaced with data from UNdata: 77 | Land area, 1000 hectares, 2002, FAO Statistics 78 | - POPULATION column (POP2005) has been replaced with data from UNdata: 79 | Population, 2005, Medium variant, World Population Prospects: The 2006 Revision 80 | - Added region and sub-region codes from UN Statistics Division. Source: 81 | http://unstats.un.org/unsd/methods/m49/m49regin.htm 82 | - Added LAT, LONG values for each country 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mapnik-server", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://gitee.com/qingyafan/mapnik-server.git" 12 | }, 13 | "author": "qingyafan@163.com", 14 | "license": "ISC", 15 | "dependencies": { 16 | "@koa/cors": "^2.2.2", 17 | "generic-pool": "^3.4.2", 18 | "koa": "^2.5.2", 19 | "koa-bodyparser": "^4.2.1", 20 | "koa-router": "^7.4.0", 21 | "mapnik": "^4.0.1" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /utils/pool.js: -------------------------------------------------------------------------------- 1 | const pool = require("generic-pool").Pool 2 | 3 | function createPool(size) { 4 | return { 5 | max: size || 5, 6 | pools: {}, 7 | acquire: (id, options, callback) => { 8 | if (!this.pools[id]) { 9 | let that = this 10 | this.pools[id] = pool({ 11 | name: id, 12 | create: options.create, 13 | destroy: options.destroy, 14 | max: that.max, 15 | idleTimeoutMillis: options.idleTimeoutMillis || 5000, 16 | log: false 17 | //reapIntervalMillis 18 | //priorityRange 19 | }) 20 | } 21 | this.pools[id].acquire(callback, options.priority); 22 | }, 23 | release: (id, obj) => { 24 | if (this.pools[id]) this.pools[id].release(obj); 25 | } 26 | } 27 | } 28 | 29 | module.exports = { 30 | createPool 31 | } -------------------------------------------------------------------------------- /utils/sphericalmercator.js: -------------------------------------------------------------------------------- 1 | const mapnik = require("mapnik") 2 | 3 | const proj4 = '+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over' 4 | const mercator = new mapnik.Projection(proj4) 5 | 6 | class SphericalMercator { 7 | 8 | constructor() { 9 | var size = 256; 10 | this.Bc = []; 11 | this.Cc = []; 12 | this.zc = []; 13 | this.Ac = []; 14 | this.DEG_TO_RAD = Math.PI / 180; 15 | this.RAD_TO_DEG = 180 / Math.PI; 16 | this.size = 256; 17 | this.levels = 18; 18 | this.proj4 = proj4; 19 | for (var d = 0; d < this.levels; d++) { 20 | this.Bc.push(size / 360); 21 | this.Cc.push(size / (2 * Math.PI)); 22 | this.zc.push(size / 2); 23 | this.Ac.push(size); 24 | size *= 2; 25 | } 26 | } 27 | 28 | minmax(a, b, c) { 29 | return Math.min(Math.max(a, b), c) 30 | } 31 | 32 | ll_to_px(ll, zoom) { 33 | let d = this.zc[zoom] 34 | let f = this.minmax(Math.sin(this.DEG_TO_RAD * ll[1]), -0.9999, 0.9999) 35 | let x = Math.round(d + ll[0] * this.Bc[zoom]); 36 | let y = Math.round(d + 0.5 * Math.log((1 + f) / (1 - f)) * (-this.Cc[zoom])); 37 | return [x, y]; 38 | } 39 | 40 | px_to_ll(px, zoom) { 41 | var zoom_denom = this.zc[zoom]; 42 | var g = (px[1] - zoom_denom) / (-this.Cc[zoom]); 43 | var lat = (px[0] - zoom_denom) / this.Bc[zoom]; 44 | var lon = this.RAD_TO_DEG * (2 * Math.atan(Math.exp(g)) - 0.5 * Math.PI); 45 | return [lat, lon]; 46 | } 47 | 48 | xyz_to_envelope(x, y, zoom, TMS_SCHEME) { 49 | if (TMS_SCHEME) { 50 | y = (Math.pow(2, zoom) - 1) - y; 51 | } 52 | var ll = [x * this.size, (y + 1) * this.size]; 53 | var ur = [(x + 1) * this.size, y * this.size]; 54 | var bbox = this.px_to_ll(ll, zoom).concat(this.px_to_ll(ur, zoom)); 55 | return mercator.forward(bbox); 56 | } 57 | 58 | } 59 | 60 | module.exports = new SphericalMercator() -------------------------------------------------------------------------------- /utils/tile.js: -------------------------------------------------------------------------------- 1 | const url = require("url") 2 | 3 | function parseXYZ(req, TMS_SCHEME, callback) { 4 | var matches = req.url.match(/(\d+)/g); 5 | if (matches && matches.length == 3) { 6 | try { 7 | var x = parseInt(matches[1], 10); 8 | var y = parseInt(matches[2], 10); 9 | var z = parseInt(matches[0], 10); 10 | if (TMS_SCHEME) 11 | y = (Math.pow(2, z) - 1) - y; 12 | callback(null, 13 | { 14 | z: z, 15 | x: x, 16 | y: y 17 | }); 18 | } catch (err) { 19 | callback(err, null); 20 | } 21 | } else { 22 | try { 23 | var query = url.parse(req.url, true).query; 24 | if (query && 25 | query.x !== undefined && 26 | query.y !== undefined && 27 | query.z !== undefined) { 28 | try { 29 | callback(null, 30 | { 31 | z: parseInt(query.z, 10), 32 | x: parseInt(query.x, 10), 33 | y: parseInt(query.y, 10) 34 | } 35 | ); 36 | } catch (err) { 37 | callback(err, null); 38 | } 39 | } else { 40 | callback(new Error('no x,y,z provided'), null); 41 | } 42 | } catch (err) { 43 | callback(err, null); 44 | } 45 | } 46 | } 47 | 48 | module.exports = { 49 | parseXYZ 50 | } -------------------------------------------------------------------------------- /vector_tile.js: -------------------------------------------------------------------------------- 1 | const mapnik = require("mapnik"); 2 | const mercator = require("./utils/sphericalmercator"); 3 | const config = require("./config"); 4 | 5 | mapnik.register_default_input_plugins() 6 | const mercatorProj4 = `+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs`; 7 | const xmlConfig = ` 8 | 9 | 10 | 11 | 12 | postgis 13 | ${config.db.host} 14 | ${config.db.port} 15 | ${config.db.database} 16 | ${config.db.user} 17 | ${config.db.password} 18 | true 19 | `; 20 | const xmlConfigTail = ` 21 | false 22 | -20037508.3427892,-20037508.3427892,20037508.3427892,20037508.3427892 23 | 24 | 25 | 26 | `; 27 | 28 | async function tile(ctx, next) { 29 | 30 | return new Promise((resolve, reject) => { 31 | let z = parseInt(ctx.params.z); 32 | let x = parseInt(ctx.params.x); 33 | let y = parseInt(ctx.params.y); 34 | const xml = parseParams(ctx.query); 35 | let map = new mapnik.Map(256, 256, mercator.proj4); 36 | 37 | map.fromString(xml, {}, (err, res) => { 38 | if (err) { 39 | console.error('style error', err) 40 | } 41 | let vt = new mapnik.VectorTile(z, x, y); 42 | map.render(vt, (err, vt) => { 43 | if (err) { 44 | console.error('render error', err); 45 | } 46 | ctx.set('Content-Type', 'application/x-protobuf'); 47 | ctx.response.status = 200; 48 | vt.getData((err, data) => { 49 | if (err) { 50 | console.log('err', err); 51 | } 52 | ctx.body = data; 53 | resolve(); 54 | }); 55 | }); 56 | }); 57 | }) 58 | } 59 | 60 | function parseParams(params) { 61 | 62 | let sql = ''; 63 | if (params) { 64 | sql += `${params.table}`; 65 | sql += `${params.geometry_field}`; 66 | } 67 | 68 | return xmlConfig + sql + xmlConfigTail; 69 | } 70 | 71 | module.exports = { 72 | tile 73 | } -------------------------------------------------------------------------------- /xml/point_vector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 35 | --------------------------------------------------------------------------------