nodetiles-core
Advanced tools
Comparing version 0.0.1 to 0.0.2
15
index.js
var Map = require(__dirname + '/lib/Map'), | ||
GeoJson = require(__dirname + '/datasources/GeoJson'), | ||
PostGIS = require(__dirname + '/datasources/GeoJson'), | ||
Shp = require(__dirname + '/datasources/Shp'), | ||
projector = require(__dirname + '/lib/projector'), | ||
routes = require(__dirname + '/lib/routes'), | ||
UTFGrid = require(__dirname + '/lib/utfgrid'); | ||
@@ -17,3 +18,3 @@ | ||
GeoJson: GeoJson, | ||
PostGIS: PostGIS | ||
Shp: Shp | ||
}, | ||
@@ -23,4 +24,8 @@ /** | ||
*/ | ||
projector: projector | ||
} | ||
projector: projector, | ||
/** | ||
* Routing Middleware | ||
*/ | ||
route: routes | ||
} |
@@ -52,13 +52,2 @@ var Canvas = require('canvas'); | ||
// For web mercator... | ||
// var pointsPerTilePoint = 2 * Math.PI * 6378137 / 256; | ||
// var tilePointsPerPoint = 256 / (2 * Math.PI * 6378137); | ||
// var tilePoints = (maxX - minX) * tilePointsPerPoint; | ||
// var tilePxPtRatio = width / tilePoints; | ||
// var zoom = Math.round(tilePxPtRatio); | ||
// console.log("tilePointsPerPoint: " + tilePointsPerPoint + | ||
// "\ntilePoints: " + tilePoints + | ||
// "\ntilePxPtRatio: " + tilePxPtRatio); | ||
// console.log("Est. Zoom: " + zoom); | ||
cartoImageRenderer(ctx, pxPtRatio, options.layers, options.styles, options.zoom, bounds.minX, bounds.maxX, bounds.minY); | ||
@@ -403,2 +392,6 @@ | ||
var closedShape = l[0][0] === l[len - 1][0] && l[0][1] === l[len - 1][1]; | ||
// NOTE: should we really bail here if there's not enough room on the line to write the text? | ||
// maybe this should be an option (on by default) | ||
var textLength = this.measureText(text.text).width; | ||
@@ -409,9 +402,33 @@ if (totalLength < textLength) { | ||
if (MIGURSKI) { | ||
var startPoint = totalLength / 2 - textLength / 2; | ||
// determine distance along line to start placing text | ||
var startPoint = 0; | ||
// TODO: support BIDI text (right now end = right and start = left) | ||
if (text.align === "end" || text.align === "right") { | ||
startPoint = totalLength - textLength; | ||
} | ||
else { | ||
var startPoint = totalLength / 2; | ||
else if (text.align === "center" || text.align === "middle" || !text.align) { | ||
startPoint = totalLength / 2 - textLength / 2; | ||
} | ||
// these operations may be destructive to the text object, so save old values here | ||
// TODO: maybe we should make a new object that uses the text argument as its prototype? | ||
var fullText = text.text; | ||
var originalXOffset = 0; | ||
if (text.offset) { | ||
originalXOffset = text.offset.x || 0; | ||
startPoint += originalXOffset; | ||
text.offset.x = 0; | ||
// if the line represents a closed shape, loop startPoint around the line | ||
if (closedShape) { | ||
while (startPoint < 0) { | ||
startPoint += totalLength; | ||
} | ||
while (startPoint > totalLength) { | ||
startPoint -= totalLength; | ||
} | ||
} | ||
} | ||
var segmentLengthsTotal = 0; | ||
@@ -421,3 +438,3 @@ | ||
var segmentLength = segmentLengths[i]; | ||
if (segmentLengthsTotal + segmentLength >= startPoint) { | ||
if (segmentLengthsTotal + segmentLength >= startPoint || i === len - 1) { | ||
var segmentDistance = startPoint - segmentLengthsTotal; | ||
@@ -450,3 +467,2 @@ | ||
if (MIGURSKI) { | ||
var fullText = text.text; | ||
var textPixels = 0; | ||
@@ -459,6 +475,6 @@ var resetIndex = 0; | ||
if (segmentOffset + segmentDistance + textPixels > segmentLength && i < len - 1) { | ||
if (segmentOffset + segmentDistance + textPixels > segmentLength && (i < len - 1 || closedShape)) { | ||
// TODO: Potentially add some spacing if the angle causes the text top to bend "in" | ||
segmentOffset = (segmentOffset + segmentDistance + textPixels) - segmentLength; | ||
i++; | ||
i = (i + 1) % len; | ||
segmentLength = segmentLengths[i]; | ||
@@ -504,6 +520,6 @@ segmentDistance = 0; | ||
if (segmentOffset + segmentDistance + textPixels > segmentLength && i < len - 1) { | ||
if (segmentOffset + segmentDistance + textPixels > segmentLength && (i < len - 1 || closedShape)) { | ||
// TODO: Potentially add some spacing if the angle causes the text top to bend "in" | ||
segmentOffset = (segmentOffset + segmentDistance + textPixels) - segmentLength; | ||
i++; | ||
i = (i + 1) % len; | ||
segmentLength = segmentLengths[i]; | ||
@@ -529,4 +545,2 @@ segmentDistance = 0; | ||
} | ||
// repair the text object! | ||
text.text = fullText; | ||
} | ||
@@ -538,6 +552,10 @@ else { | ||
this.restore(); | ||
return; | ||
break; | ||
} | ||
segmentLengthsTotal += segmentLength; | ||
} | ||
// repair text object before returning | ||
text.text = fullText; | ||
text.offset.x = originalXOffset; | ||
} | ||
@@ -575,17 +593,2 @@ else { | ||
var cartoImageRenderer = function (ctx, scale, layers, styles, zoom, minX, maxX, minY) { | ||
// var length = (maxX - minX) / 5; | ||
// renderDashedPath.LineString.call(ctx, [4, 4], | ||
// [[minX, minY], | ||
// [minX + length, minY], | ||
// [minX + length + length, minY], | ||
// [minX + length + length + length, minY], | ||
// [minX + length + length + length + length, minY]]); | ||
// ctx.strokeStyle = "#000"; | ||
// ctx.lineWidth = 1.0; | ||
// // ctx.lineCap = "square"; | ||
// ctx.stroke(); | ||
// didATile = true; | ||
// return; | ||
// background first | ||
@@ -615,2 +618,4 @@ styles.forEach(function(style) { | ||
// create list of attachments (in order) so that we can walk through and render them together for each layer | ||
// TODO: should be able to do this as part of the processing stage | ||
var attachments = styles.reduce(function(attachmentList, style) { | ||
@@ -835,2 +840,3 @@ if (style.attachment !== "__default__" && attachmentList.indexOf(style.attachment) === -1) { | ||
} | ||
textInfo.align = ctx.textAlign; | ||
@@ -837,0 +843,0 @@ // text-transform |
@@ -41,3 +41,3 @@ var async = require("async"); | ||
if (error) { | ||
callback(error); | ||
options.callback(error); | ||
} | ||
@@ -61,3 +61,3 @@ else { | ||
if (error) { | ||
callback(error); | ||
options.callback(error); | ||
console.error("ERROR! "+error); | ||
@@ -64,0 +64,0 @@ } |
{ | ||
"name": "nodetiles-core", | ||
"version": "0.0.1", | ||
"version": "0.0.2", | ||
"description": "Joyful map rendering with Node.js.", | ||
@@ -23,22 +23,25 @@ "contributors": [ | ||
"type": "git", | ||
"url": "https://github.com/codeforamerica/nodetiles.git" | ||
"url": "https://github.com/nodetiles/nodetiles-core.git" | ||
}, | ||
"engines": { | ||
"node": ">0.6.x", | ||
"npm": "1.1.x" | ||
"node": ">0.8.x", | ||
"npm": ">1.1.x" | ||
}, | ||
"dependencies": { | ||
"async": "~0.1.x", | ||
"lodash": "~0.6.1", | ||
"canvas": "0.12.x", | ||
"async": "~0.2", | ||
"lodash": "~1.2", | ||
"canvas": "~1.0", | ||
"proj4js":"~0.3.0", | ||
"proj4js-defs":">=0.0.1", | ||
"pg":"~0.8.4", | ||
"carto":"~0.9.3" | ||
"carto":"~0.9.3", | ||
"shp":"https://github.com/yuletide/node-shp/tarball/master" | ||
}, | ||
"devDependencies": { | ||
"mocha": "~1.4.1", | ||
"chai": "~1.2.0", | ||
"sinon": "~1.4.2" | ||
"mocha": "~1.9", | ||
"chai": "~1.5", | ||
"sinon": "~1.6" | ||
}, | ||
"scripts": { | ||
"test": "mocha" | ||
} | ||
} |
@@ -1,53 +0,77 @@ | ||
NodeTiles | ||
Nodetiles-Core | ||
============= | ||
Nodetiles-core is a javascript library for rendering map tiles suitable for slippy-maps and static images. Features include: | ||
This is a dirty hack to do server-side map tile rendering using geojson/shapefiles. Map tiles are rendered using node-canvas--a [special version](https://github.com/bensheldon/node-canvas-heroku) that includes a pre-compiled Cairo binary that works on Heroku--and requested via a standard leaflet map. This demo currently renders Baltimore neighbrhoods. | ||
- **Flexible Data-connectors**: We offer GeoJson and Shapefile connectors out-of-the-box, but it's easy to build your own. | ||
- **Map Projections**: Transform data between more [3,900+](https://github.com/yuletide/node-proj4js-defs/blob/master/epsg.js) EPSG projections using Proj4.js | ||
- **CartoCSS Support**: We support many (if not most) stylesheet features of [CartCSS](http://mapbox.com/tilemill/docs/manual/carto/) making it trivial to import your map styles from tools like Tilemill | ||
- **Slippy-map URL helpers**: Easily serve map tiles, UTFGrids, and Tile.json. Check out [nodetiles-init](https://github.com/nodetiles/nodetiles-init) for a simple scaffold. | ||
- **Static maps**: If slipply-maps aren't your style, generate static images of any dimension; checkout [nodetiles-example-static](https://github.com/nodetiles/nodetiles-example-static) for examples. | ||
- **Joyfully simple, pluggable, flexible, powerful**: We built Nodetiles to be easily understandable, extensible and a joy to use. It's built with Javascript and tries to provide a solid foundation of tools that are still easy to understand, extend or replace depending on your needs. [File an issue](https://github.com/nodetiles/nodetiles-core/issues/new) if Nodetiles can't do what you need. | ||
Big THANKS to [Tom Carden](https://github.com/RandomEtc) whose [original gist](https://gist.github.com/668577) inspired this project. He also has other very [useful](https://github.com/RandomEtc/nodemap) [projects](https://github.com/RandomEtc/shapefile-js). | ||
Screenshot | ||
------- | ||
Installation on Heroku | ||
---------------------- | ||
![Nodetiles Screenshot](https://raw.github.com/nodetiles/nodetiles-core/master/screenshot.png) | ||
1. Clone it | ||
2. Within the directory, `heroku create --stack cedar` | ||
3. Setup your heroku environment variables for node-canvas-heroku | ||
$ heroku config:add LD_PRELOAD='/app/node_modules/canvas/cairo/libcairo.so /app/node_modules/canvas/lib/libpixman-1.so.0 /app/node_modules/canvas/lib/libfreetype.so.6' --app <your-app> | ||
$ heroku config:add LD_LIBRARY_PATH=/app/node_modules/canvas/cairo --app <your-app> | ||
IMPORTANT: replace the `<your-app>` at the end of each command with your Heroku app's name, e.g. 'furious-sparrow-2089' | ||
4. `git push heroku master` | ||
5. Rejoice / Open an issue that these instructions are inadequate | ||
Local Development | ||
----------------- | ||
Example | ||
------- | ||
``` | ||
/* Set up the libraries */ | ||
var nodetiles = require('nodetiles-core'), | ||
GeoJsonSource = nodetiles.datasources.GeoJson, | ||
Projector = nodetiles.projector, | ||
fs = require('fs'); // we'll output to a file | ||
/* Create your map context */ | ||
var map = new nodetiles.Map({ | ||
projection: "EPSG:4326" // set the projection of the map | ||
}); | ||
For local development, use `npm install --dev`, which will install node-canvas instead of node-canvas-heroku. | ||
/* Add some data */ | ||
map.addData(new GeoJsonSource({ | ||
name: "world", | ||
path: __dirname + '/countries.geojson', | ||
projection: "EPSG:900913" | ||
})); | ||
*Note: node-canvas requires Cairo, which you will need to install separately.* | ||
/* Link your Carto stylesheet */ | ||
map.addStyle(fs.readFileSync('./style.mss','utf8')); | ||
How it works / How to modify it | ||
------------------------------- | ||
/* Render out the map to a file */ | ||
map.render({ | ||
// Make sure your bounds are in the same projection as the map | ||
bounds: {minX: -180, minY: -90, maxX: 180, maxY: 90}, | ||
width: 800, // number of pixels to output | ||
height: 400, | ||
callback: function(err, canvas) { | ||
var file = fs.createWriteStream(__dirname + '/map.png'), | ||
stream = canvas.createPNGStream(); | ||
This application renders PNG map tiles as well as UTFGrid interaction tiles from static geojson files (though it would be relatively trivial to load geojson from a live database instead). Those geojson are loaded into the `Layers` list along with some basic style settings. When a specific tile is requested, a new canvas is created and the Layers, then Features are stepped through and drawn to a canvas, which is then streamed back to the requesting client as a PNG. UTFGrids are rendered in much the same way: they are drawn as a raster where each feature is assigned a unique RGB value; the resulting raster is then read out to create a UTFGrid. | ||
stream.on('data', function(chunk){ | ||
file.write(chunk); | ||
}); | ||
Shapefiles | ||
---------- | ||
stream.on('end', function(){ | ||
console.log('Saved map.png!'); | ||
}); | ||
} | ||
}); | ||
Within the `/geodata` directory are GEOjson files from the [datasf.of](https://data.sfgov.org/) (transformed via ogr2ogr). | ||
* San Francisco Shorelines | ||
* San Francisco Street Centerlines | ||
* San Francisco Parks | ||
* San Francisco Parcels (is huge and crashes Heroku when loaded; >500MB memory) | ||
``` | ||
And some others, including world outlines from [Natural Earth](http://www.naturalearthdata.com/). | ||
Thanks | ||
------- | ||
Big THANKS to [Tom Carden](https://github.com/RandomEtc) whose [original gist](https://gist.github.com/668577) inspired this project. He also has other very [useful](https://github.com/RandomEtc/nodemap) [projects](https://github.com/RandomEtc/shapefile-js). | ||
Projections | ||
----------- | ||
[Supported projections](https://github.com/temsa/node-proj4js/tree/master/lib/defs) | ||
[Supported projections](https://github.com/yuletide/node-proj4js-defs) | ||
Copyright | ||
--------- | ||
Copyright (c) 2012-2013 Code for America. See LICENSE for details. | ||
@@ -94,3 +94,3 @@ var expect = require('chai').expect; | ||
[-13633655.37301766, 4546834.601778074], | ||
[-13637201.900674844, 4544625.309289526], | ||
[-13637201.900674844, 4544625.309289527], | ||
[-13633092.207713738, 4546097.273993165] ] | ||
@@ -97,0 +97,0 @@ ] |
HTTP dependency
Supply chain riskContains a dependency which resolves to a remote HTTP URL which could be used to inject untrusted code and reduce overall package reliability.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
spdx disjunction for an artifact's license information
Licensespdx disjunction for an artifact's license information
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 2 instances in 1 package
spdx disjunction for an artifact's license information
Licensespdx disjunction for an artifact's license information
Found 1 instance in 1 package
78
13
248211
17
2340
1
1
+ Addedshp@https://github.com/yuletide/node-shp/tarball/master
+ Addedasync@0.2.10(transitive)
+ Addedcanvas@1.0.4(transitive)
+ Addedlodash@1.2.1(transitive)
- Removedpg@~0.8.4
- Removedasync@0.1.22(transitive)
- Removedcanvas@0.12.1(transitive)
- Removedgeneric-pool@1.0.12(transitive)
- Removedlodash@0.6.1(transitive)
- Removedpg@0.8.8(transitive)
Updatedasync@~0.2
Updatedcanvas@~1.0
Updatedlodash@~1.2