Skip to content

Instantly share code, notes, and snippets.

@lwu
Created August 12, 2008 05:18
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save lwu/5009 to your computer and use it in GitHub Desktop.
Save lwu/5009 to your computer and use it in GitHub Desktop.
example code demonstrating Mapnik C++ API
This code snippet, modifed from Mpanik's rundemo.cpp (r727, license LGPL),
renders the State of California using USGS state boundaries data.
Here's a play-by-play (via https://lists.berlios.de/pipermail/mapnik-devel/2008-August/000679.html)
rundemo.cpp renders a map of Ontario/Quebec, and instead I wanted to draw a
map of California and its surrounding states.
I grabbed the data from the USGS in the form of "statesp020" (google it, or
browse through nationalatlas.gov) and first wanted to modify rundemo.py
instead. Alas, this is uncharted territory, as the Python binding does not
yet support the mapnik::query object (see the near empty
mapnik/bindings/python/mapnik/mapnik_query.cpp). Instead, I modified
demo/c++/rundemo.cpp)
It took some time to get up to speed on Mapnik internals -- the code is
sparsely commented and there doesn't seem to be a canonical Doxygen dump
online (although one can be generated manually from the source). The Mapnik
wiki has some details, and I started to fill in some small gaps there, but
important bits such as what the "res" (second) parameter to the
mapnik::query ctor isn't documented anywhere (elsewhere, some feature
to_string() method seems to return "TODO").
The basic Mapnik data model which isn't directly documented is something
like this. A mapnik Map has Layers, a Layer has a mapnik::datasource_ptr,
and a datasource has features. One queries a Map's Layer's datasource by
asking it for the features given a mapnik::query, specifying the
axis-aligned bounding box (Envelope).
Mapnik relies heavily on boost shared_ptr and similar classes, with the
convention that _ptr denotes such a pointer.
You can follow along here -- http://gist.github.com/5009#LID76
Note the call to (mapnik::query) q.add_property_name('STATE') on Line 80 --
without these, no properties will be returned! Figured this out by reading
feature_style_processor.hpp.
Line 81 has the feature query call. It would probably be nice to use a
filter_featureset<> object instead, but the filter<> objects don't seem to
expose public typedefs to facilitate declaring the filter_featureset<>
object?
So, we asked the Map's Layer's datasource for a featureset_ptr, and then
iterate through the linked list of features, checking to see which features
pass the previously constructed filter. Unfortunately, the "nil" extent,
that is, a default constructed Envelope<double> isn't really a nil extent,
but rather an Envelope with negative width and negative height. That's okay
but you shouldn't tell such an Envelope to expand_to_include() another
Envelope, as it will do the Wrong Thing.
I was hoping that it would work the other way, but there may or may not be
reasons to design the library this way (code simplicity).
Overall, even though the library is as-of-yet sparsely documented, it seems
to work pretty well even though my XCode compiles crash whereas gcc compiles
don't (ostensibly with the same include, compile, and linking options). The
Python binding has some rough edges, and there are cases where appropriate
public typedefs (in the "traits" spirit) would make figuring out how to type
things somewhat simpler.
~L
/*****************************************************************************
* Example code modified from Mapnik rundemo.cpp (r727, license LGPL).
* Renders the State of California using USGS state boundaries data.
*****************************************************************************/
// define before any includes
#define BOOST_SPIRIT_THREADSAFE
#include <mapnik/map.hpp>
#include <mapnik/datasource_cache.hpp>
#include <mapnik/font_engine_freetype.hpp>
#include <mapnik/agg_renderer.hpp>
#include <mapnik/filter_factory.hpp>
#include <mapnik/color_factory.hpp>
#include <mapnik/image_util.hpp>
#include <mapnik/config_error.hpp>
#include <iostream>
int main ( int argc , char** argv)
{
if (argc != 2)
{
std::cout << "usage: $0 <mapnik_install_dir>\n";
return EXIT_SUCCESS;
}
using namespace mapnik;
try {
std::string mapnik_dir(argv[1]);
datasource_cache::instance()->register_datasources(mapnik_dir + "/plugins/input/shape");
freetype_engine::register_font(mapnik_dir + "/fonts/dejavu-ttf-2.14/DejaVuSans.ttf");
Map m(800,600);
m.set_background(color_factory::from_string("cadetblue"));
// create styles
// States (polygon)
feature_type_style cali_style;
rule_type cali_rule;
filter_ptr cali_filter = create_filter("[STATE] = 'California'");
cali_rule.set_filter(cali_filter);
cali_rule.append(polygon_symbolizer(Color(250, 190, 183)));
cali_style.add_rule(cali_rule);
// Provinces (polyline)
feature_type_style provlines_style;
stroke provlines_stk (Color(0,0,0),1.0);
provlines_stk.add_dash(8, 4);
provlines_stk.add_dash(2, 2);
provlines_stk.add_dash(2, 2);
rule_type provlines_rule;
provlines_rule.append(polygon_symbolizer(color_factory::from_string("cornsilk")));
provlines_rule.append(line_symbolizer(provlines_stk));
provlines_rule.set_filter(create_filter("[STATE] <> 'California'"));
cali_style.add_rule(provlines_rule);
m.insert_style("cali",cali_style);
// Layers
// Provincial polygons
{
parameters p;
p["type"]="shape";
p["file"]="../data/statesp020"; // State Boundaries of the United States [SHP]
Layer lyr("Cali");
lyr.set_datasource(datasource_cache::instance()->create(p));
lyr.add_style("cali");
m.addLayer(lyr);
}
// Get layer's featureset
Layer lay = m.getLayer(0);
mapnik::datasource_ptr ds = lay.datasource();
mapnik::query q(lay.envelope(), 1.0 /* what does this "res" param do? */);
q.add_property_name("STATE"); // NOTE: Without this call, no properties will be found!
mapnik::featureset_ptr fs = ds->features(q);
// One day, use filter_featureset<> instead?
Envelope<double> extent; // NOTE: nil extent = <[0,0], [-1,-1]>
// Loop through features in the set, get extent of those that match query && filter
feature_ptr feat = fs->next();
filter_ptr cali_f(create_filter("[STATE] = 'California'"));
std::cerr << cali_f->to_string() << std::endl; // NOTE: prints (STATE=TODO)!
while (feat) {
if (cali_f->pass(*feat)) {
for (unsigned i=0; i<feat->num_geometries();++i) {
geometry2d & geom = feat->get_geometry(i);
if (extent.width() < 0 && extent.height() < 0) {
// NOTE: expand_to_include() broken w/ nil extent. Work around.
extent = geom.envelope();
}
extent.expand_to_include(geom.envelope());
}
}
feat = fs->next();
}
m.zoomToBox(extent);
m.zoom(1.2); // zoom out slightly
Image32 buf(m.getWidth(),m.getHeight());
agg_renderer<Image32> ren(m,buf);
ren.apply();
save_to_file<ImageData32>(buf.data(),"cali.png","png");
}
catch ( const mapnik::config_error & ex )
{
std::cerr << "### Configuration error: " << ex.what();
return EXIT_FAILURE;
}
catch ( const std::exception & ex )
{
std::cerr << "### std::exception: " << ex.what();
return EXIT_FAILURE;
}
catch ( ... )
{
std::cerr << "### Unknown exception." << std::endl;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
g++ -g -I/usr/local/include/mapnik -I/opt/local/include -I/opt/local/include/freetype2 -I../../agg/include -L/usr/local/lib -L/opt/local/lib -lmapnik -lboost_thread-mt -licuuc cali.cpp -o cali
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment