node-mapserv
is a Node.js module which makes the functionality provided by
the traditional Mapserver mapserv
CGI program available to Node code.
node-mapserv
is best thought of as a wrapper around CGI mapserv
: internally
it uses a C++ binding to shave away the thin layer representing the binary
mapserv
CGI interface, replacing it with a javascript interface. All the
underlying mapserv
logic and code remain the same.
node-mapserv
is not MapScript for Node. Instead it provides a simple
declarative API for rendering mapserver mapfiles with the following benefits:
-
All the considerable functionality of CGI
mapserv
are at your disposal: there's no need to reinvent the wheel (maybe just add a few spokes here and there!). -
Support for declarative mapfile programming: most of what can be accomplished imperatively using mapscript can be done declaratively by custom generating new mapfiles and tweaking existing mapfiles (see this post for a more detailed discussion).
-
Adherence to the Node non-blocking philosophy: operations involving I/O (such as parsing mapfiles and rendering maps) are performed asynchronously in child threads which keeps the main event loop snappy. Rough benchmarks suggest performance is comparable to fastcgi mapserv (using
examples/wms-server.js
with a modified mapfile). -
Robustly tested: The module has a suite of tests that exercises the whole API. The tests provide 96% line coverage and 95% function coverage; excluded code generally handles hard to replicate edge cases (e.g. memory exhaustion). This suite has been run through Valgrind to check for memory leaks.
It is assumed that you are familiar with
using mapserv
and
creating mapfiles.
The API is simple and defines the following general pattern:
-
Instantiate an instance of the
Map
class from a mapfile on the filesystem (usingMap.FromFile
) or a mapfile string (usingMap.FromString
). -
Render the mapfile as many times as required using
Map.mapserv
. This function emulates Mapserver CGImapserv
functionality and as such requires the creation of CGI environment variables to define the request parameters.
The following example illustrates these steps:
var mapserv = require('../lib/mapserv'), // the Mapserv module
fs = require('fs'), // for filesystem operations
// A minimalist mapfile string
mapfile = "MAP \
NAME hello \
STATUS ON \
EXTENT 0 0 4000 3000 \
SIZE 400 300 \
IMAGECOLOR 200 255 255 \
LAYER \
NAME 'credits' \
STATUS DEFAULT \
TRANSFORM FALSE \
TYPE ANNOTATION \
FEATURE \
POINTS \
200 150 \
END \
TEXT 'Hello world. Mapserver rocks.' \
END \
CLASS \
LABEL \
TYPE BITMAP \
COLOR 0 0 0 \
END \
END \
END \
END";
// Instantiate a Map object from the mapfile string. You could use
// `mapserv.Map.FromFile` instead.
mapserv.Map.FromString(mapfile, function handleMap(err, map) {
if (err) throw err; // error loading the mapfile
// a minimal CGI environment
var env = {
REQUEST_METHOD: 'GET',
QUERY_STRING: 'mode=map&layer=credits'
};
map.mapserv(env, function handleMapResponse(err, mapResponse) {
if (err) {
throw err; // error generating the response
}
// If the response is an image, save it to a file, otherwise write it
// to standard output.
var contentType = mapResponse.headers['Content-Type'][0]; // get the content type from the headers
if (contentType.substr(0, 5) === 'image') {
var filename = 'output.' + contentType.substr(6); // get the file extension from the content type
fs.writeFile(filename, mapResponse.data, function onWrite(err) {
if (err) {
throw err; // error writing to file
}
console.log('Mapserver response written to `%s`', filename);
});
} else {
console.log(mapResponse.data.toString());
}
});
});
Editing the mapfile string and using it to instantiate new Map
objects will
of course allow you to produce different maps.
The above example can be found in the package as examples/hello-world.js
.
examples/wms-server.js
provides a more full featured example that marries
node-mapserv
with the stock Node http
module to create a cascading WMS
server. In addition it illustrates:
-
How to pass a 'body' string to
Map.mapserv
representing the HTTP body of a request (e.g. used in HTTP POST and PUT requests). -
The use of the
mapserv.createCGIEnvironment
function used to generate a CGI environment from anhttp.ServerRequest
object.
Versioning information is also available. From the Node REPL:
> var mapserv = require('mapserv');
> mapserv.versions
{ node_mapserv: '0.1.3',
mapserver: '6.3-dev',
mapserver_numeric: 60300,
mapserver_details: 'MapServer version 6.3-dev OUTPUT=PNG OUTPUT=JPEG SUPPORTS=PROJ SUPPORTS=AGG SUPPORTS=FREETYPE SUPPORTS=CAIRO SUPPORTS=ICONV SUPPORTS=FRIBIDI SUPPORTS=WMS_SERVER SUPPORTS=WFS_SERVER SUPPORTS=WCS_SERVER SUPPORTS=FASTCGI SUPPORTS=THREADS SUPPORTS=GEOS INPUT=JPEG INPUT=POSTGIS INPUT=OGR INPUT=GDAL INPUT=SHAPEFILE' }
Errors generated by Mapserver include a number of useful details. This
information is exposed by the following properties which are present on all
errors returned by the module and named MapserverError
(i.e. all errors
passed to a callback):
code
: an integer representing the error categorycategory
: a description of the error categoryroutine
: the name of the Mapserver routine generating the errorisReported
: a boolean flagging whether the error has been rendered in any Mapserver outputerrorStack
: an array containing the stack of any errors leading up to the current error, most recent error first. This property is not available in errors present in the stack itself.
-
Linux OS (although it should work on other Unices with minimal effort - patches welcome!). Although not currently supported, the module has been built successfully on Windows - see
WINDOWS-INSTALL.md
for more details. -
Node.js 0.10 (Not yet working with 0.12)
-
Mapserver >= 6.2. If you are using
Map.FromString
ensure that you are using Mapserver >= 6.2.1 or alternatively you have applied this patch. Mapserver must be compiled with support for threads.
The latest stable release of Node Mapcache is available via the Node Package Manager:
-
Ensure Node.js and Mapserver are available on your system. Mapserver will need to have been built from source with the source directory still available.
-
Point
node-mapserv
to the Mapserver source directory. It uses the build files to configure itself during installation. E.g.npm config set mapserv:build_dir /tmp/mapserver-6.2
-
Get and install
node-mapserv
:npm install mapserv
-
Optionally test that everything is working as expected (recommended):
npm test mapserv
Assuming you have Docker available on your system, the following command will obtain a docker image with the latest Node Mapserv code from git built against the latest Mapserver git checkout:
docker pull homme/node-mapserv:latest
Note that the latest Mapserver git checkout is the latest at the time the image was built. If you want the absolute latest code of both Node Mapserv and Mapserver, build the docker image yourself locally along these lines:
docker build -t homme/node-mapserv:latest https://raw.github.com/geo-data/node-mapserv/master/docker/latest/Dockerfile
By default the image runs the Node Mapserv tests:
docker run homme/node-mapserv:latest
Running Node directly from the image allows you to require()
and
play around with Node Mapserv interactively:
docker run -t -i homme/node-mapserv:latest /usr/bin/node
> var mapserv = require('mapserv');
undefined
> mapserv.versions
{ node_mapserv: '0.1.4',
mapserver: '6.5-dev',
mapserver_numeric: 60500,
mapserver_details: 'MapServer version 6.5-dev OUTPUT=PNG OUTPUT=JPEG OUTPUT=KML SUPPORTS=PROJ SUPPORTS=AGG SUPPORTS=FREETYPE SUPPORTS=CAIRO SUPPORTS=SVG_SYMBOLS SUPPORTS=SVGCAIRO SUPPORTS=ICONV SUPPORTS=XMP SUPPORTS=FRIBIDI SUPPORTS=WMS_SERVER SUPPORTS=WMS_CLIENT SUPPORTS=WFS_SERVER SUPPORTS=WFS_CLIENT SUPPORTS=WCS_SERVER SUPPORTS=SOS_SERVER SUPPORTS=THREADS SUPPORTS=GEOS INPUT=JPEG INPUT=POSTGIS INPUT=OGR INPUT=GDAL INPUT=SHAPEFILE' }
See the Docker Index for further information.
-
Avoid using Mapserver features that are not thread safe:
node-mapserv
makes heavy use of threads and although this is safe for core mapserver operations, some extended features should be avoided. See the Mapserver FAQ and GitHub issues #4041 and #4044 for further details. -
Become familiar with Mapserver runtime substitution: this allows you to alter portions of a mapfile based on data passed via a CGI request.
-
Use the
PROCESSING "CLOSE_CONNECTION=DEFER"
directive in you mapfiles in order to cache data connections where possible:Map
instances wrap persistent mapfile data structures and can therefore benefit from pooling persistent data connections in the same way as fastcgi mapserv. -
Check out
node-mapcache
: this can work well in combination withnode-mapserv
for generating tiled maps.
Fork the code on GitHub or clone it:
git clone https://github.com/geo-data/node-mapserv.git
cd node-mapserv
Build the module in Debug mode using:
make build
By default this uses the Mapserver build directory previously specified using
npm config set mapserv:build_dir
; to override this do something along the
following lines:
make build npm_config_mapserv_build_dir=/tmp/mapserver-6.2
You may want to ensure you're building in a clean source tree in which case:
make clean
Add tests for your changes to test/mapserv-test.js
and run them:
make test
Perform code coverage analysis to ensure all code paths in your changes are
tested (this requires lcov
be
installed):
make cover
Finally run the test suite through valgrind
to ensure you haven't introduced
any memory issues:
make valgrind
And issue your pull request or patch...
Doxygen based documentation is available for the C++ bindings:
make doc
Please report bugs or issues using the GitHub issue tracker.
Homme Zwaagstra [email protected]