This is an automated email from the git hooks/post-receive script. kapouer pushed a commit to branch master in repository mapnik-vector-tile.
commit 0d87195f497dd13621e9152756e00e5c4bcb45ee Author: Jérémy Lal <kapo...@melix.org> Date: Wed Sep 3 03:28:11 2014 +0200 Imported Upstream version 0.5.2+dfsg --- .travis.yml | 10 +- CHANGELOG.md | 4 + Makefile | 20 +-- README.md | 20 +-- examples/python/hello-world.py | 40 ----- package.json | 2 +- python/__init__.py | 0 python/renderer.py | 314 ---------------------------------------- src/mapnik3x_compatibility.hpp | 7 + src/vector_tile_backend_pbf.hpp | 9 +- src/vector_tile_datasource.hpp | 45 +++--- test/raster_tile.cpp | 1 - 12 files changed, 54 insertions(+), 418 deletions(-) diff --git a/.travis.yml b/.travis.yml index 04883b0..eb72b61 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,7 @@ compiler: - gcc before_install: - - sudo apt-add-repository --yes ppa:mapnik/v2.2.0 + #- sudo apt-add-repository --yes ppa:mapnik/v2.2.0 - sudo apt-add-repository --yes ppa:mapnik/nightly-2.3 #- sudo apt-add-repository --yes ppa:mapnik/nightly-trunk - sudo apt-get update -y @@ -15,10 +15,10 @@ install: #- sudo apt-get -y install gcc-4.8 g++-4.8 before_script: - - sudo apt-get -y install libmapnik=2.2.0* mapnik-utils=2.2.0* libmapnik-dev=2.2.0* - - make clean - - make test - - sudo apt-get purge libmapnik=2.2.0* mapnik-utils=2.2.0* libmapnik-dev=2.2.0* + #- sudo apt-get -y install libmapnik=2.2.0* mapnik-utils=2.2.0* libmapnik-dev=2.2.0* + #- make clean + #- make test + #- sudo apt-get purge libmapnik=2.2.0* mapnik-utils=2.2.0* libmapnik-dev=2.2.0* script: - sudo apt-get -y install libmapnik=2.3.0* mapnik-utils=2.3.0* libmapnik-dev=2.3.0* mapnik-input-plugin*=2.3.0* diff --git a/CHANGELOG.md b/CHANGELOG.md index c22ad80..495c3db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changlog +## 0.5.2 + + - Build fixes to work against latest Mapnik 3.x + ## 0.5.1 - Minor build fixes diff --git a/Makefile b/Makefile index 2b2203f..d6f07d6 100755 --- a/Makefile +++ b/Makefile @@ -17,24 +17,16 @@ mapnik-vector-tile: src/vector_tile.pb.cc Makefile src/vector_tile.pb.cc: proto/vector_tile.proto protoc -Iproto/ --cpp_out=./src proto/vector_tile.proto -python/vector_tile_pb2.py: proto/vector_tile.proto - protoc -Iproto/ --python_out=python proto/vector_tile.proto - -python: python/vector_tile_pb2.py - test/run-test: Makefile src/vector_tile.pb.cc test/vector_tile.cpp test/test_utils.hpp src/* - @$(CXX) -o ./test/run-test test/vector_tile.cpp src/vector_tile.pb.cc -I./src $(CXXFLAGS) $(MAPNIK_CXXFLAGS) $(PROTOBUF_CXXFLAGS) $(COMMON_FLAGS) $(MAPNIK_LDFLAGS) $(PROTOBUF_LDFLAGS) $(LDFLAGS) -Wno-unused-private-field + $(CXX) -o ./test/run-test test/vector_tile.cpp src/vector_tile.pb.cc -I./src $(CXXFLAGS) $(MAPNIK_CXXFLAGS) $(PROTOBUF_CXXFLAGS) $(COMMON_FLAGS) $(MAPNIK_LDFLAGS) $(PROTOBUF_LDFLAGS) $(LDFLAGS) -Wno-unused-private-field -test/test-cfg.h: - echo "#define MAPNIK_PLUGINDIR \"$(MAPNIK_PLUGINDIR)\"" > test/test-cfg.h - -test: test/test-cfg.h test/run-test test/run-geom-test ./test/run-raster-test src/vector_tile.pb.cc test/catch.hpp +test: test/run-test test/run-geom-test ./test/run-raster-test src/vector_tile.pb.cc test/catch.hpp ./test/run-test ./test/run-geom-test ./test/run-raster-test ./test/run-raster-test: Makefile src/vector_tile.pb.cc test/raster_tile.cpp test/encoding_util.hpp test/catch.hpp - @$(CXX) -o ./test/run-raster-test test/raster_tile.cpp src/vector_tile.pb.cc -I./src $(CXXFLAGS) $(MAPNIK_CXXFLAGS) $(PROTOBUF_CXXFLAGS) $(COMMON_FLAGS) $(MAPNIK_LDFLAGS) $(PROTOBUF_LDFLAGS) $(LDFLAGS) -Wno-unused-private-field + $(CXX) -o ./test/run-raster-test test/raster_tile.cpp src/vector_tile.pb.cc -DMAPNIK_PLUGINDIR=\"$(MAPNIK_PLUGINDIR)\" -I./src $(CXXFLAGS) $(MAPNIK_CXXFLAGS) $(PROTOBUF_CXXFLAGS) $(COMMON_FLAGS) $(MAPNIK_LDFLAGS) $(PROTOBUF_LDFLAGS) $(LDFLAGS) -Wno-unused-private-field ./test/run-raster-test ./test/run-geom-test: Makefile src/vector_tile.pb.cc test/geometry_encoding.cpp test/encoding_util.hpp src/vector_tile_geometry_encoder.hpp test/catch.hpp @@ -43,16 +35,12 @@ test: test/test-cfg.h test/run-test test/run-geom-test ./test/run-raster-test sr geom: test/run-geom-test src/vector_tile.pb.cc ./test/run-geom-test -python-test: python/vector_tile_pb2.py - python ./test/python/test.py - clean: @rm -f ./src/vector_tile.pb.cc @rm -f ./src/vector_tile.pb.h + @rm -f ./test/test-cfg.h @rm -f ./test/run-test @rm -f ./test/run-geom-test @rm -f ./test/run-raster-test - @rm -f ./python/vector_tile_pb2.py - @rm -f ./python/*pyc .PHONY: test diff --git a/README.md b/README.md index f2f9142..0314ef4 100644 --- a/README.md +++ b/README.md @@ -51,22 +51,6 @@ Run the C++ tests like: See examples in examples/c++ -### Python - -These require the protobuf python bindings and the `rtree` library -which can be installed like: - - sudo apt-get install libspatialindex-dev - pip install protobuf rtree - -To build and test the python example code do: - - make python && make python-test - -Run the example code: - - python examples/python/hello-world.py - ## Authors - [Artem Pavlenko](https://github.com/artemp) @@ -76,5 +60,5 @@ Run the example code: ## See also - http://mike.teczno.com/notes/postgreslessness-mapnik-vectiles.html -- https://github.com/mdaines/ceramic -- https://github.com/opensciencemap/VectorTileMap +- https://github.com/jones139/ceramic +- https://github.com/opensciencemap/vtm diff --git a/examples/python/hello-world.py b/examples/python/hello-world.py deleted file mode 100755 index 08b41ef..0000000 --- a/examples/python/hello-world.py +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -import sys -import json -# put `./lib` dir on path -sys.path.append("./python") - -import renderer - -if __name__ == "__main__" : - # create a single tile at 0/0/0.png like tile.osm.org/0/0/0.png - zoom = 0 - x = 0 - y = 0 - # request object holds a Tile XYZ and internally holds mercator extent - req = renderer.Request(x,y,zoom) - # create a vector tile, given a tile request - vtile = renderer.VectorTile(req) - # for a given point representing a spot in NYC - lat = 40.70512 - lng = -74.01226 - # and some attributes - attr = {"hello":"world"} - # convert to mercator coords - x,y = renderer.lonlat2merc(lng,lat) - # add this point and attributes to the tile - vtile.add_point(x,y,attr) - # print the protobuf as geojson just for debugging - # NOTE: coordinate rounding is by design and - print 'GeoJSON representation of tile (purely for debugging):' - print vtile.to_geojson() - print '-'*60 - # print the protobuf message as a string - print 'Protobuf string representation of tile (purely for debugging):' - print vtile - print '-'*60 - # print the protobuf message, zlib encoded - print 'Serialized, deflated tile message (storage and wire format):' - print vtile.to_message().encode('zlib') diff --git a/package.json b/package.json index 82f8ffe..aea5e4c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mapnik-vector-tile", - "version": "0.5.1", + "version": "0.5.2", "description": "Mapnik vector tile API", "main": "./package.json", "repository" : { diff --git a/python/__init__.py b/python/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/python/renderer.py b/python/renderer.py deleted file mode 100755 index c265d89..0000000 --- a/python/renderer.py +++ /dev/null @@ -1,314 +0,0 @@ -#!/usr/bin/env python - -import sys -import gzip -import json -import os -import math -import vector_tile_pb2 - -class Box2d(object): - """Box2d object to represent floating point bounds as - - minx,miny,maxx,maxy - - Which we can also think of as: - - left,bottom,right,top - west,south,east,north - """ - def __init__(self,minx,miny,maxx,maxy): - self.minx = float(minx) - self.miny = float(miny) - self.maxx = float(maxx) - self.maxy = float(maxy) - - def width(self): - return self.maxx - self.minx - - def height(self): - return self.maxy - self.miny - - def bounds(self): - return [self.minx,self.miny,self.maxx,self.maxy] - - def intersects(self, x, y): - return not (x > self.maxx or x < self.minx or y > self.maxy or y < self.miny) - - def __repr__(self): - return 'Box2d(%s,%s,%s,%s)' % (self.minx,self.miny,self.maxx,self.maxy) - -# Max extent edge for spherical mercator -MAX_EXTENT = 20037508.342789244 -DEG_TO_RAD = math.pi/180 -RAD_TO_DEG = 180/math.pi - -def lonlat2merc(lon,lat): - "Convert coordinate pair from epsg:4326 to epsg:3857" - x = lon * MAX_EXTENT / 180 - y = math.log(math.tan((90 + lat) * math.pi / 360)) / DEG_TO_RAD - y = y * MAX_EXTENT / 180 - return [x,y] - -def merc2lonlat(x,y): - "Convert coordinate pair from epsg:3857 to epsg:4326" - x = (x / MAX_EXTENT) * 180; - y = (y / MAX_EXTENT) * 180; - y = RAD_TO_DEG * (2 * math.atan(math.exp(y * DEG_TO_RAD)) - math.pi/2); - return x,y - -def minmax(a,b,c): - a = max(a,b) - a = min(a,c) - return a - -class SphericalMercator(object): - """ - Core definition of Spherical Mercator Projection. - - Adapted from: - http://svn.openstreetmap.org/applications/rendering/mapnik/generate_tiles.py - """ - def __init__(self, levels=22, size=256): - self.Bc = [] - self.Cc = [] - self.zc = [] - self.Ac = [] - self.levels = levels - self.size = size - for d in range(0,levels+1): - e = size/2.0; - self.Bc.append(size/360.0) - self.Cc.append(size/(2.0 * math.pi)) - self.zc.append((e,e)) - self.Ac.append(size) - size *= 2.0 - - def ll_to_px(self, px, zoom): - """ Convert LatLong (EPSG:4326) to pixel postion """ - d = self.zc[zoom] - e = round(d[0] + px[0] * self.Bc[zoom]) - f = minmax(math.sin(DEG_TO_RAD * px[1]),-0.9999,0.9999) - g = round(d[1] + 0.5 * math.log((1+f)/(1-f))*-self.Cc[zoom]) - return (e,g) - - def px_to_ll(self, px, zoom): - """ Convert pixel postion to LatLong (EPSG:4326) """ - e = self.zc[zoom] - f = (px[0] - e[0])/self.Bc[zoom] - g = (px[1] - e[1])/-self.Cc[zoom] - h = RAD_TO_DEG * ( 2 * math.atan(math.exp(g)) - 0.5 * math.pi) - return (f,h) - - def bbox(self, x, y, zoom): - """ Convert XYZ to extent in mercator """ - ll = (x * self.size,(y + 1) * self.size) - ur = ((x + 1) * self.size, y * self.size) - minx,miny = self.px_to_ll(ll,zoom) - maxx,maxy = self.px_to_ll(ur,zoom) - minx,miny = lonlat2merc(minx,miny) - maxx,maxy = lonlat2merc(maxx,maxy) - return [minx,miny,maxx,maxy] - - def xyz(self, bbox, zoom): - """ Convert extent in mercator to XYZ extents""" - minx,miny,maxx,maxy = bbox - ll = merc2lonlat(minx,miny) - ur = merc2lonlat(maxx,maxy) - px_ll = self.ll_to_px(ll,zoom) - px_ur = self.ll_to_px(ur,zoom) - return [int(math.floor(px_ll[0]/self.size)), - int(math.floor(px_ur[1]/self.size)), - int(math.floor((px_ur[0]-1)/self.size)), - int(math.floor((px_ll[1]-1)/self.size)) - ] - -class Request(object): - """ - Request encapulates a single tile request in the common OSM, aka XYZ scheme. - - Interally we convert the x,y,zoom to a mercator bounding box assuming a 256 pixel tile - """ - def __init__(self, x, y, zoom): - assert isinstance(zoom,int) - assert zoom <= 22 - assert isinstance(x,int) - assert isinstance(y,int) - self.x = x - self.y = y - self.zoom = zoom - self.size = 256 - self.mercator = SphericalMercator(levels=22,size=self.size) - self.extent = Box2d(*self.mercator.bbox(x,y,zoom)) - - def get_extent(self): - return self.extent - - def get_width(self): - return self.extent.width() - - def get_height(self): - return self.extent.height() - - def bounds(self): - return self.extent.bounds() - -class CoordTransform(object): - """ - CoordTransform provides methods for converting coordinate pairs - to and from a geographical coordinate system (usually mercator) - to screen or pixel coordinates for a given tile request. - """ - def __init__(self,request): - assert isinstance(request,Request) - self.extent = request.get_extent() - self.sx = (float(request.size) / request.get_width()) - self.sy = (float(request.size) / request.get_height()) - - def forward(self,x,y): - """Geo coordinates to Screen coordinates""" - x0 = (x - self.extent.minx) * self.sx - y0 = (self.extent.maxy - y) * self.sy - return x0,y0 - - def backward(self,x,y): - """Screen coordinates to Geo coordinates""" - x0 = self.extent.minx + x / self.sx - y0 = self.extent.maxy - y / self.sy - return x0,y0 - -class VectorTile(object): - """ - VectorTile is object that makes it easy to turn a sequence of - point features into a vector tile using optimized encoding for - transport over the wire and later rendering by MapBox tools. - - """ - def __init__(self, req, path_multiplier=16): - assert isinstance(req,Request) - self.request = req - self.extent = self.request.extent - self.ctrans = CoordTransform(req) - self.path_multiplier = path_multiplier - self.tile = vector_tile_pb2.tile() - self.layer = self.tile.layers.add() - self.layer.name = "points" - self.layer.version = 2 - self.layer.extent = req.size * self.path_multiplier # == 4096 - self.feature_count = 0 - self.keys = [] - self.values = [] - self.pixels = [] - - def __str__(self): - return self.tile.__str__() - - def to_message(self): - return self.tile.SerializeToString() - - def _decode_coords(self, dx, dy): - x = ((dx >> 1) ^ (-(dx & 1))) - y = ((dy >> 1) ^ (-(dy & 1))) - x,y = float(x)/self.path_multiplier,float(y)/self.path_multiplier - x,y = self.ctrans.backward(x,y); - return x,y - - def _encode_coords(self, x, y, rint=False): - dx,dy = self.ctrans.forward(x,y) - if rint: - dx = int(round(dx * self.path_multiplier)) - dy = int(round(dy * self.path_multiplier)) - else: - dx = int(math.floor(dx * self.path_multiplier)) - dy = int(math.floor(dy * self.path_multiplier)) - dxi = (dx << 1) ^ (dx >> 31) - dyi = (dy << 1) ^ (dy >> 31) - #assert dxi >= 0 and dxi <= self.layer.extent - #assert dyi >= 0 and dyi <= self.layer.extent - return dxi,dyi - - def add_point(self, x, y, properties,skip_coincident=True,rint=False): - if self.extent.intersects(x,y): - dx,dy = self._encode_coords(x,y,rint=rint) - # TODO - use numpy matrix to "paint" points so we - # can drop all coincident ones except last - key = "%d-%d" % (dx,dy) - if key not in self.pixels: - f = self.layer.features.add() - self.feature_count += 1 - f.id = self.feature_count - f.type = self.tile.Point - self._handle_attr(self.layer,f,properties) - f.geometry.append((1 << 3) | (1 & ((1 << 3) - 1))) - f.geometry.append(dx) - f.geometry.append(dy) - self.pixels.append(key) - return True - else: - raise RuntimeError("point does not intersect with tile bounds") - return False - - def to_geojson(self,lonlat=False): - jobj = {} - jobj['type'] = "FeatureCollection" - features = [] - for feat in self.layer.features: - fobj = {} - fobj['type'] = "Feature" - properties = {} - for i in xrange(0,len(feat.tags),2): - key_id = feat.tags[i] - value_id = feat.tags[i+1] - name = str(self.layer.keys[key_id]) - val = self.layer.values[value_id] - if val.HasField('bool_value'): - properties[name] = val.bool_value - elif val.HasField('string_value'): - properties[name] = val.string_value - elif val.HasField('int_value'): - properties[name] = val.int_value - elif val.HasField('float_value'): - properties[name] = val.float_value - elif val.HasField('double_value'): - properties[name] = val.double_value - else: - raise Exception("Unknown value type: '%s'" % val) - fobj['properties'] = properties - x,y = self._decode_coords(feat.geometry[1],feat.geometry[2]) - if lonlat: - x,y = merc2lonlat(x,y) - fobj['geometry'] = { - "type":"Point", - "coordinates": [x,y] - } - features.append(fobj) - jobj['features'] = features - return json.dumps(jobj,indent=4) - - def _handle_attr(self, layer, feature, props): - for k,v in props.items(): - if k not in self.keys: - layer.keys.append(k) - self.keys.append(k) - idx = self.keys.index(k) - feature.tags.append(idx) - else: - idx = self.keys.index(k) - feature.tags.append(idx) - if v not in self.values: - if (isinstance(v,bool)): - val = layer.values.add() - val.bool_value = v - elif (isinstance(v,str)) or (isinstance(v,unicode)): - val = layer.values.add() - val.string_value = v - elif (isinstance(v,int)): - val = layer.values.add() - val.int_value = v - elif (isinstance(v,float)): - val = layer.values.add() - val.double_value = v - else: - raise Exception("Unknown value type: '%s'" % type(v)) - self.values.append(v) - feature.tags.append(self.values.index(v)) diff --git a/src/mapnik3x_compatibility.hpp b/src/mapnik3x_compatibility.hpp index d2d88ec..6765579 100644 --- a/src/mapnik3x_compatibility.hpp +++ b/src/mapnik3x_compatibility.hpp @@ -1,6 +1,7 @@ #ifndef __MAPNIK_VECTOR_TILE_COMPATIBILITY_H__ #define __MAPNIK_VECTOR_TILE_COMPATIBILITY_H__ +#include <mapnik/config.hpp> #include <mapnik/version.hpp> #if MAPNIK_VERSION >= 300000 @@ -16,6 +17,9 @@ #define MAPNIK_POLYGON mapnik::geometry_type::types::Polygon #define MAPNIK_LINESTRING mapnik::geometry_type::types::LineString #define MAPNIK_UNKNOWN mapnik::geometry_type::types::Unknown + #define MAPNIK_VARIANT_INCLUDE <mapnik/util/variant.hpp> + #define MAPNIK_APPLY_VISITOR mapnik::util::apply_visitor + #define MAPNIK_STATIC_VISITOR mapnik::util::static_visitor #else #define MAPNIK_UNIQUE_PTR std::auto_ptr #define MAPNIK_SHARED_INCLUDE <boost/shared_ptr.hpp> @@ -29,6 +33,9 @@ #define MAPNIK_POLYGON mapnik::Polygon #define MAPNIK_LINESTRING mapnik::LineString #define MAPNIK_UNKNOWN mapnik::Unknown + #define MAPNIK_VARIANT_INCLUDE <boost/variant.hpp> + #define MAPNIK_APPLY_VISITOR boost::apply_visitor + #define MAPNIK_STATIC_VISITOR boost::static_visitor #endif #endif diff --git a/src/vector_tile_backend_pbf.hpp b/src/vector_tile_backend_pbf.hpp index f49d0c4..e2577b2 100644 --- a/src/vector_tile_backend_pbf.hpp +++ b/src/vector_tile_backend_pbf.hpp @@ -1,6 +1,9 @@ #ifndef __MAPNIK_VECTOR_TILE_BACKEND_PBF_H__ #define __MAPNIK_VECTOR_TILE_BACKEND_PBF_H__ +#include "mapnik3x_compatibility.hpp" +#include MAPNIK_VARIANT_INCLUDE + // mapnik #include <mapnik/feature.hpp> #include <mapnik/value_types.hpp> @@ -13,11 +16,11 @@ #include <boost/unordered_map.hpp> #include <boost/foreach.hpp> -#include "mapnik3x_compatibility.hpp" +#include MAPNIK_VARIANT_INCLUDE namespace mapnik { namespace vector { - struct to_tile_value: public boost::static_visitor<> + struct to_tile_value: public MAPNIK_STATIC_VISITOR<> { public: to_tile_value(tile_value * value): @@ -134,7 +137,7 @@ namespace mapnik { namespace vector { { // The value doesn't exist yet in the dictionary. to_tile_value visitor(current_layer_->add_values()); - boost::apply_visitor(visitor, val.base()); + MAPNIK_APPLY_VISITOR(visitor, val.base()); size_t index = values_.size(); values_.insert(values_container::value_type(val, index)); diff --git a/src/vector_tile_datasource.hpp b/src/vector_tile_datasource.hpp index 001d291..bc878d5 100644 --- a/src/vector_tile_datasource.hpp +++ b/src/vector_tile_datasource.hpp @@ -140,28 +140,33 @@ namespace mapnik { namespace vector { MAPNIK_UNIQUE_PTR<mapnik::image_reader> reader(mapnik::get_image_reader(image_buffer.data(),image_buffer.size())); if (reader.get()) { - if (f.has_id()) + int image_width = reader->width(); + int image_height = reader->height(); + if (image_width > 0 && image_height > 0) { - feature_id = f.id(); + if (f.has_id()) + { + feature_id = f.id(); + } + #if MAPNIK_VERSION >= 300000 + double filter_factor = 1.0; + #endif + bool premultiplied = false; + mapnik::feature_ptr feature = mapnik::feature_factory::create(ctx_,feature_id); + mapnik::raster_ptr raster = MAPNIK_MAKE_SHARED<mapnik::raster>( + tile_extent_, + image_width, + image_height, + #if MAPNIK_VERSION >= 300000 + filter_factor, + #endif + premultiplied + ); + reader->read(0,0,raster->data_); + feature->set_raster(raster); + add_attributes(feature,f,layer_,tr_); + return feature; } - #if MAPNIK_VERSION >= 300000 - double filter_factor = 1.0; - #endif - bool premultiplied = false; - mapnik::feature_ptr feature = mapnik::feature_factory::create(ctx_,feature_id); - mapnik::raster_ptr raster = MAPNIK_MAKE_SHARED<mapnik::raster>( - tile_extent_, - reader->width(), - reader->height(), - #if MAPNIK_VERSION >= 300000 - filter_factor, - #endif - premultiplied - ); - reader->read(0,0,raster->data_); - feature->set_raster(raster); - add_attributes(feature,f,layer_,tr_); - return feature; } } if (f.geometry_size() <= 0) diff --git a/test/raster_tile.cpp b/test/raster_tile.cpp index 47e3bbb..e813093 100644 --- a/test/raster_tile.cpp +++ b/test/raster_tile.cpp @@ -4,7 +4,6 @@ // test utils #include "test_utils.hpp" -#include "test-cfg.h" #include "vector_tile_projection.hpp" -- Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-grass/mapnik-vector-tile.git _______________________________________________ Pkg-grass-devel mailing list Pkg-grass-devel@lists.alioth.debian.org http://lists.alioth.debian.org/cgi-bin/mailman/listinfo/pkg-grass-devel