| 1 | # $Id$ |
|---|
| 2 | # |
|---|
| 3 | # This file is part of Mapnik (c++ mapping toolkit) |
|---|
| 4 | # Copyright (C) 2005 Jean-Francois Doyon |
|---|
| 5 | # |
|---|
| 6 | # Mapnik is free software; you can redistribute it and/or |
|---|
| 7 | # modify it under the terms of the GNU General Public License |
|---|
| 8 | # as published by the Free Software Foundation; either version 2 |
|---|
| 9 | # of the License, or any later version. |
|---|
| 10 | # |
|---|
| 11 | # This program is distributed in the hope that it will be useful, |
|---|
| 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|---|
| 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|---|
| 14 | # GNU General Public License for more details. |
|---|
| 15 | # |
|---|
| 16 | # You should have received a copy of the GNU General Public License |
|---|
| 17 | # along with this program; if not, write to the Free Software |
|---|
| 18 | # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
|---|
| 19 | |
|---|
| 20 | # Import everything. In this case this is safe, in more complex systems, you |
|---|
| 21 | # will want to be more selective. |
|---|
| 22 | |
|---|
| 23 | try: |
|---|
| 24 | from mapnik import * |
|---|
| 25 | except: |
|---|
| 26 | print '\n\nThe mapnik library and python bindings must have been compiled and \ |
|---|
| 27 | installed successfully before running this script.\n\n' |
|---|
| 28 | raise |
|---|
| 29 | |
|---|
| 30 | # Instanciate a map, giving it a width and height. Remember: the word "map" is |
|---|
| 31 | # reserved in Python! :) |
|---|
| 32 | |
|---|
| 33 | m = Map(800,600,"+proj=latlong +ellps=WGS84") |
|---|
| 34 | |
|---|
| 35 | # Set its background colour. More on colours later ... |
|---|
| 36 | |
|---|
| 37 | m.background = Color('white') |
|---|
| 38 | |
|---|
| 39 | # Now we can start adding layers, in stacking order (i.e. bottom layer first) |
|---|
| 40 | |
|---|
| 41 | # Canadian Provinces (Polygons) |
|---|
| 42 | |
|---|
| 43 | # Instanciate a layer. The parameters depend on the type of data: |
|---|
| 44 | # shape: |
|---|
| 45 | # type='shape' |
|---|
| 46 | # file='/path/to/shape' |
|---|
| 47 | # raster: |
|---|
| 48 | # type='raster' |
|---|
| 49 | # file='/path/to/raster' |
|---|
| 50 | # postgis: |
|---|
| 51 | # type='postgis' |
|---|
| 52 | # host='127.0.0.1' |
|---|
| 53 | # dbname='mydatabase' |
|---|
| 54 | # user='myusername' |
|---|
| 55 | # password='mypassword' |
|---|
| 56 | # table= TODO |
|---|
| 57 | |
|---|
| 58 | provpoly_lyr = Layer('Provinces') |
|---|
| 59 | provpoly_lyr.datasource = Shapefile(file='../data/boundaries', encoding='latin1') |
|---|
| 60 | |
|---|
| 61 | # We then define a style for the layer. A layer can have one or many styles. |
|---|
| 62 | # Styles are named, so they can be shared across different layers. |
|---|
| 63 | # Multiple styles per layer behaves functionally like multiple layers. The |
|---|
| 64 | # data is completely re-scanned for each style within one layer, and a style |
|---|
| 65 | # will be drawn entirely "above" the previous one. Performance wise using |
|---|
| 66 | # multiple styles in one layer is the same has having multiple layers. |
|---|
| 67 | # The paradigm is useful mostly as a convenience. |
|---|
| 68 | |
|---|
| 69 | provpoly_style = Style() |
|---|
| 70 | |
|---|
| 71 | # A Style needs one or more rules. A rule will normally consist of a filter |
|---|
| 72 | # for feature selection, and one or more symbolizers. |
|---|
| 73 | |
|---|
| 74 | provpoly_rule_on = Rule() |
|---|
| 75 | |
|---|
| 76 | # A Filter() allows the selection of features to which the symbology will |
|---|
| 77 | # be applied. More on Mapnik expressions can be found in Tutorial #2. |
|---|
| 78 | # A given feature can only match one filter per rule per style. |
|---|
| 79 | |
|---|
| 80 | provpoly_rule_on.filter = Filter("[NAME_EN] = 'Ontario'") |
|---|
| 81 | |
|---|
| 82 | # Here a symbolizer is defined. Available are: |
|---|
| 83 | # - LineSymbolizer(Color(),<width>) |
|---|
| 84 | # - LineSymbolizer(Stroke()) |
|---|
| 85 | # - PolygonSymbolizer(Color()) |
|---|
| 86 | # - PointSymbolizer(<file>,<type>,<width>,<height>) |
|---|
| 87 | |
|---|
| 88 | # Some of them can accept a Color() instance, which can be created with: |
|---|
| 89 | # - Color(<red>, <green>, <blue>) |
|---|
| 90 | # - Color(<red>, <green>, <blue>, <alpha>) |
|---|
| 91 | # - Color(<string>) where <string> will be something like '#00FF00' |
|---|
| 92 | # or '#0f0' or 'green' |
|---|
| 93 | |
|---|
| 94 | provpoly_rule_on.symbols.append(PolygonSymbolizer(Color(250, 190, 183))) |
|---|
| 95 | provpoly_style.rules.append(provpoly_rule_on) |
|---|
| 96 | |
|---|
| 97 | provpoly_rule_qc = Rule() |
|---|
| 98 | provpoly_rule_qc.filter = Filter("[NAME_EN] = 'Quebec'") |
|---|
| 99 | provpoly_rule_qc.symbols.append(PolygonSymbolizer(Color(217, 235, 203))) |
|---|
| 100 | provpoly_style.rules.append(provpoly_rule_qc) |
|---|
| 101 | |
|---|
| 102 | # Add the style to the map, giving it a name. This is the name that will be |
|---|
| 103 | # used to refer to it from here on. Having named styles allows them to be |
|---|
| 104 | # re-used throughout the map. |
|---|
| 105 | |
|---|
| 106 | m.append_style('provinces', provpoly_style) |
|---|
| 107 | |
|---|
| 108 | # Then associate the style to the layer itself. |
|---|
| 109 | |
|---|
| 110 | provpoly_lyr.styles.append('provinces') |
|---|
| 111 | |
|---|
| 112 | # Then add the layer to the map. In reality, it's the order in which you |
|---|
| 113 | # append them to the map that will determine the drawing order, though by |
|---|
| 114 | # convention it is recommended to define them in drawing order as well. |
|---|
| 115 | |
|---|
| 116 | m.layers.append(provpoly_lyr) |
|---|
| 117 | |
|---|
| 118 | # Drainage |
|---|
| 119 | |
|---|
| 120 | # A simple example ... |
|---|
| 121 | |
|---|
| 122 | qcdrain_lyr = Layer('Quebec Hydrography') |
|---|
| 123 | |
|---|
| 124 | qcdrain_lyr.datasource = Shapefile(file='../data/qcdrainage') |
|---|
| 125 | |
|---|
| 126 | qcdrain_style = Style() |
|---|
| 127 | qcdrain_rule = Rule() |
|---|
| 128 | qcdrain_rule.filter = Filter('[HYC] = 8') |
|---|
| 129 | qcdrain_rule.symbols.append(PolygonSymbolizer(Color(153, 204, 255))) |
|---|
| 130 | qcdrain_style.rules.append(qcdrain_rule) |
|---|
| 131 | |
|---|
| 132 | m.append_style('drainage', qcdrain_style) |
|---|
| 133 | qcdrain_lyr.styles.append('drainage') |
|---|
| 134 | m.layers.append(qcdrain_lyr) |
|---|
| 135 | |
|---|
| 136 | # In this case, we have 2 data sets with similar schemas (same filtering |
|---|
| 137 | # attributes, and same desired style), so we're going to |
|---|
| 138 | # re-use the style defined in the above layer for the next one. |
|---|
| 139 | |
|---|
| 140 | ondrain_lyr = Layer('Ontario Hydrography') |
|---|
| 141 | ondrain_lyr.datasource = Shapefile(file='../data/ontdrainage') |
|---|
| 142 | |
|---|
| 143 | ondrain_lyr.styles.append('drainage') |
|---|
| 144 | m.layers.append(ondrain_lyr) |
|---|
| 145 | |
|---|
| 146 | # Provincial boundaries |
|---|
| 147 | |
|---|
| 148 | provlines_lyr = Layer('Provincial borders') |
|---|
| 149 | provlines_lyr.datasource = Shapefile(file='../data/boundaries_l') |
|---|
| 150 | |
|---|
| 151 | # Here we define a "dash dot dot dash" pattern for the provincial boundaries. |
|---|
| 152 | |
|---|
| 153 | provlines_stk = Stroke() |
|---|
| 154 | provlines_stk.add_dash(8, 4) |
|---|
| 155 | provlines_stk.add_dash(2, 2) |
|---|
| 156 | provlines_stk.add_dash(2, 2) |
|---|
| 157 | provlines_stk.color = Color('black') |
|---|
| 158 | provlines_stk.width = 1.0 |
|---|
| 159 | |
|---|
| 160 | provlines_style = Style() |
|---|
| 161 | provlines_rule = Rule() |
|---|
| 162 | provlines_rule.symbols.append(LineSymbolizer(provlines_stk)) |
|---|
| 163 | provlines_style.rules.append(provlines_rule) |
|---|
| 164 | |
|---|
| 165 | m.append_style('provlines', provlines_style) |
|---|
| 166 | provlines_lyr.styles.append('provlines') |
|---|
| 167 | m.layers.append(provlines_lyr) |
|---|
| 168 | |
|---|
| 169 | # Roads 3 and 4 (The "grey" roads) |
|---|
| 170 | |
|---|
| 171 | roads34_lyr = Layer('Roads') |
|---|
| 172 | # create roads datasource (we're going to re-use it later) |
|---|
| 173 | |
|---|
| 174 | roads34_lyr.datasource = Shapefile(file='../data/roads') |
|---|
| 175 | |
|---|
| 176 | roads34_style = Style() |
|---|
| 177 | roads34_rule = Rule() |
|---|
| 178 | roads34_rule.filter = Filter('[CLASS] = 3 or [CLASS] = 4') |
|---|
| 179 | |
|---|
| 180 | # With lines of a certain width, you can control how the ends |
|---|
| 181 | # are closed off using line_cap as below. |
|---|
| 182 | |
|---|
| 183 | roads34_rule_stk = Stroke() |
|---|
| 184 | roads34_rule_stk.color = Color(171,158,137) |
|---|
| 185 | roads34_rule_stk.line_cap = line_cap.ROUND_CAP |
|---|
| 186 | |
|---|
| 187 | # Available options are: |
|---|
| 188 | # line_cap: BUTT_CAP, SQUARE_CAP, ROUND_CAP |
|---|
| 189 | # line_join: MITER_JOIN, MITER_REVERT_JOIN, ROUND_JOIN, BEVEL_JOIN |
|---|
| 190 | |
|---|
| 191 | # And one last Stroke() attribute not used here is "opacity", which |
|---|
| 192 | # can be set to a numerical value. |
|---|
| 193 | |
|---|
| 194 | roads34_rule_stk.width = 2.0 |
|---|
| 195 | roads34_rule.symbols.append(LineSymbolizer(roads34_rule_stk)) |
|---|
| 196 | roads34_style.rules.append(roads34_rule) |
|---|
| 197 | |
|---|
| 198 | m.append_style('smallroads', roads34_style) |
|---|
| 199 | roads34_lyr.styles.append('smallroads') |
|---|
| 200 | m.layers.append(roads34_lyr) |
|---|
| 201 | |
|---|
| 202 | # Roads 2 (The thin yellow ones) |
|---|
| 203 | |
|---|
| 204 | roads2_lyr = Layer('Roads') |
|---|
| 205 | |
|---|
| 206 | # Just get a copy from roads34_lyr |
|---|
| 207 | roads2_lyr.datasource = roads34_lyr.datasource |
|---|
| 208 | |
|---|
| 209 | roads2_style_1 = Style() |
|---|
| 210 | roads2_rule_1 = Rule() |
|---|
| 211 | roads2_rule_1.filter = Filter('[CLASS] = 2') |
|---|
| 212 | roads2_rule_stk_1 = Stroke() |
|---|
| 213 | roads2_rule_stk_1.color = Color(171,158,137) |
|---|
| 214 | roads2_rule_stk_1.line_cap = line_cap.ROUND_CAP |
|---|
| 215 | roads2_rule_stk_1.width = 4.0 |
|---|
| 216 | roads2_rule_1.symbols.append(LineSymbolizer(roads2_rule_stk_1)) |
|---|
| 217 | roads2_style_1.rules.append(roads2_rule_1) |
|---|
| 218 | |
|---|
| 219 | m.append_style('road-border', roads2_style_1) |
|---|
| 220 | |
|---|
| 221 | roads2_style_2 = Style() |
|---|
| 222 | roads2_rule_2 = Rule() |
|---|
| 223 | roads2_rule_2.filter = Filter('[CLASS] = 2') |
|---|
| 224 | roads2_rule_stk_2 = Stroke() |
|---|
| 225 | roads2_rule_stk_2.color = Color(255,250,115) |
|---|
| 226 | roads2_rule_stk_2.line_cap = line_cap.ROUND_CAP |
|---|
| 227 | roads2_rule_stk_2.width = 2.0 |
|---|
| 228 | roads2_rule_2.symbols.append(LineSymbolizer(roads2_rule_stk_2)) |
|---|
| 229 | roads2_style_2.rules.append(roads2_rule_2) |
|---|
| 230 | |
|---|
| 231 | m.append_style('road-fill', roads2_style_2) |
|---|
| 232 | |
|---|
| 233 | roads2_lyr.styles.append('road-border') |
|---|
| 234 | roads2_lyr.styles.append('road-fill') |
|---|
| 235 | |
|---|
| 236 | m.layers.append(roads2_lyr) |
|---|
| 237 | |
|---|
| 238 | # Roads 1 (The big orange ones, the highways) |
|---|
| 239 | |
|---|
| 240 | roads1_lyr = Layer('Roads') |
|---|
| 241 | roads1_lyr.datasource = roads34_lyr.datasource |
|---|
| 242 | |
|---|
| 243 | roads1_style_1 = Style() |
|---|
| 244 | roads1_rule_1 = Rule() |
|---|
| 245 | roads1_rule_1.filter = Filter('[CLASS] = 1') |
|---|
| 246 | roads1_rule_stk_1 = Stroke() |
|---|
| 247 | roads1_rule_stk_1.color = Color(188,149,28) |
|---|
| 248 | roads1_rule_stk_1.line_cap = line_cap.ROUND_CAP |
|---|
| 249 | roads1_rule_stk_1.width = 7.0 |
|---|
| 250 | roads1_rule_1.symbols.append(LineSymbolizer(roads1_rule_stk_1)) |
|---|
| 251 | roads1_style_1.rules.append(roads1_rule_1) |
|---|
| 252 | m.append_style('highway-border', roads1_style_1) |
|---|
| 253 | |
|---|
| 254 | roads1_style_2 = Style() |
|---|
| 255 | roads1_rule_2 = Rule() |
|---|
| 256 | roads1_rule_2.filter = Filter('[CLASS] = 1') |
|---|
| 257 | roads1_rule_stk_2 = Stroke() |
|---|
| 258 | roads1_rule_stk_2.color = Color(242,191,36) |
|---|
| 259 | roads1_rule_stk_2.line_cap = line_cap.ROUND_CAP |
|---|
| 260 | roads1_rule_stk_2.width = 5.0 |
|---|
| 261 | roads1_rule_2.symbols.append(LineSymbolizer(roads1_rule_stk_2)) |
|---|
| 262 | roads1_style_2.rules.append(roads1_rule_2) |
|---|
| 263 | |
|---|
| 264 | m.append_style('highway-fill', roads1_style_2) |
|---|
| 265 | |
|---|
| 266 | roads1_lyr.styles.append('highway-border') |
|---|
| 267 | roads1_lyr.styles.append('highway-fill') |
|---|
| 268 | |
|---|
| 269 | m.layers.append(roads1_lyr) |
|---|
| 270 | |
|---|
| 271 | # Populated Places |
|---|
| 272 | |
|---|
| 273 | popplaces_lyr = Layer('Populated Places') |
|---|
| 274 | popplaces_lyr.datasource = Shapefile(file='../data/popplaces',encoding='latin1') |
|---|
| 275 | |
|---|
| 276 | popplaces_style = Style() |
|---|
| 277 | popplaces_rule = Rule() |
|---|
| 278 | |
|---|
| 279 | # And here we have a TextSymbolizer, used for labeling. |
|---|
| 280 | # The first parameter is the name of the attribute to use as the source of the |
|---|
| 281 | # text to label with. Then there is font size in points (I think?), and colour. |
|---|
| 282 | |
|---|
| 283 | popplaces_text_symbolizer = TextSymbolizer('GEONAME', |
|---|
| 284 | 'DejaVu Sans Book', |
|---|
| 285 | 10, Color('black')) |
|---|
| 286 | |
|---|
| 287 | # We set a "halo" around the text, which looks like an outline if thin enough, |
|---|
| 288 | # or an outright background if large enough. |
|---|
| 289 | popplaces_text_symbolizer.set_label_placement=label_placement.POINT_PLACEMENT |
|---|
| 290 | popplaces_text_symbolizer.halo_fill = Color('white') |
|---|
| 291 | popplaces_text_symbolizer.halo_radius = 1 |
|---|
| 292 | popplaces_rule.symbols.append(popplaces_text_symbolizer) |
|---|
| 293 | |
|---|
| 294 | popplaces_style.rules.append(popplaces_rule) |
|---|
| 295 | |
|---|
| 296 | m.append_style('popplaces', popplaces_style) |
|---|
| 297 | popplaces_lyr.styles.append('popplaces') |
|---|
| 298 | m.layers.append(popplaces_lyr) |
|---|
| 299 | |
|---|
| 300 | # Draw map |
|---|
| 301 | |
|---|
| 302 | # Set the initial extent of the map. |
|---|
| 303 | |
|---|
| 304 | m.zoom_to_box(Envelope(1405120.04127408,-247003.813399447,1706357.31328276,-25098.593149577)) |
|---|
| 305 | |
|---|
| 306 | # Render two maps, two PNGs, one JPEG. |
|---|
| 307 | im = Image(m.width,m.height) |
|---|
| 308 | render(m, im) |
|---|
| 309 | |
|---|
| 310 | # Save image to files |
|---|
| 311 | images = [] |
|---|
| 312 | im.save('demo.png', 'png') # true-colour RGBA |
|---|
| 313 | images.append('demo.png') |
|---|
| 314 | im.save('demo256.png', 'png256') # save to palette based (max 256 colours) png |
|---|
| 315 | images.append('demo256.png') |
|---|
| 316 | im.save('demo.jpg', 'jpeg') |
|---|
| 317 | images.append('demo.jpg') |
|---|
| 318 | |
|---|
| 319 | # Render cairo examples |
|---|
| 320 | try: |
|---|
| 321 | import cairo |
|---|
| 322 | surface = cairo.SVGSurface('demo.svg', m.width,m.height) |
|---|
| 323 | render(m, surface) |
|---|
| 324 | images.append('demo.svg') |
|---|
| 325 | surface = cairo.PDFSurface('demo.pdf', m.width,m.height) |
|---|
| 326 | render(m, surface) |
|---|
| 327 | images.append('demo.pdf') |
|---|
| 328 | except: |
|---|
| 329 | print '\n\nSkipping cairo examples as Pycairo not available' |
|---|
| 330 | |
|---|
| 331 | print "\n\n", len(images), "maps have been rendered in the current directory:" |
|---|
| 332 | for image in images: |
|---|
| 333 | print "-", image |
|---|
| 334 | print "\n\nHave a look!\n\n" |
|---|