June 30, 2012
Maperitive: Alpenglow Effect Using A Custom Shader

The latest release comes with a new Python script called Alpenglow.py which renders a hillshading relief adding some yellowish tint to illuminated high slopes to enhance the 3D effect. It was inspired by the “See the light: How to make illuminated shaded relief in Photoshop 6.0” article on the excellent shadedrelief.com site.

You can run the script by executing

run-python Samples/Python/Alpenglow.py

The script generates the hillshading for a fixed map area in Switzerland, but you can change this quite easily in the script. You can also change other parameters, like shading (and alpenglow) colors, elevation ranges etc.

The script also demonstrates the power of writing a custom shader. A shader transforms a small part of the digital elevation model (DEM) into a pixel color, based on the elevation, aspect and the slope of that part of the DEM. This way you can write your own hillshading effects function and then supply it to the generator. Here’s how it’s done for Alpenglow.py:

# Execute the hillshading
bitmap = ReliefUtils.generate_relief_bitmap(dem, relief_bbox, Srid.WebMercator, 1, alpenglow)

The last parameter of the generate_relief_bitmap function is the shader function, in our case called alpenglow:

# The actual shader function which is called for each pixel of the hillshading bitmap.
# It receives the elevation, the aspect (orientation) of the slope and the steepnes (slope).
def alpenglow(elevation, aspect, slope):
    sin_slope = math.sin (slope)

    nx = math.cos(aspect) * sin_slope
    ny = math.sin(aspect) * sin_slope
    nz = math.cos(slope)

    direction = dotproduct(nx, ny, nz, lx, ly, lz)

    direction = (direction - minDirection) / (1 - minDirection);
    if direction < 0:
        direction = 0

    # This is how we can mix two colors together.
    shade_color = shade_base_color.mix (light_color, direction)

    if direction > 0.0 and elevation > min_alpenglow_elevation:
        elevation_factor = min((elevation - min_alpenglow_elevation) / delta_alpenglow_elevation, 1);

        alpenglow_factor = math.pow(direction, 4)
        return shade_color.mix (alpenglow_color, elevation_factor * alpenglow_factor).argb;

    return shade_color.argb;

October 11, 2011
Maperitive: maperipy Progress

WARNING: this is one of those teaser-posts - you get to hear about the cool new features, but you won’t be able to try them out, at least not very soon.

Introduction

In the last few weeks since the beta release I’ve been working on a custom job involving Maperitive. I won’t go into details about the job itself, but the important thing is that this job was an opportunity for maperipy Python API to be extended with some new features. I’ll go through some of them, together with sample Python code.

Geometries

maperipy provides classes for basic geometries (compatible with OGC’s Simple Features Specification) like Point, LineString, LinearRing, Polygon, MultiPolygon etc. I’ve tried to stick to the syntax and semantics used by shapely library (unfortunately shapely itself cannot be used directly because it won’t run in IronPython - it depends on some C code in the backstage, but more about this some other time).

Custom Layers

maperipy now allows users to create their own custom map layers which can be programmatically filled with map symbols using geometries described previously:

# we first create a custom layer on a map
layer = map.add_custom_layer()

# create a simple geometry
line = LineString([(0, 0), (0, 10), (10, 10), (10, 0)])

# add a symbol for it...
symbol = LineSymbol("my_line", (line, ))
# ... and define the visual style
symbol.pen_width = 5
symbol.pen_color = Color("red")

# now we add the symbol to the layer
layer.add_symbol(layer)

Shapefiles

The custom job required the data to be loaded from shapefiles and then rendered on a map based on geometry attributes. This can now be achieved in maperipy:

# first we read the shapefile...
land_use = store.load_shapefile(r"LandUse.shp")

# ... and add polygons for a specific land use type
symbol = PolygonSymbol(
    "forest", 
    land_use.find_polygons(lambda p : p["land_use_type"] == 4000))
symbol.style.fill_color = symbol.style.pen_color = Color("green")
layer.add_symbol(symbol)

Rasters

maperipy supports several types of raster grid file formats: SRTM HGT, ESRI ASCII grid and XYZ. They can now all be used to generate hillshadings or relief contours.

Example code which reads a set of XYZ files, merges them together and then generates hillshading image from them:

parts = []

dem_dir = r"DEM"

for file_name in os.listdir(dem_dir):
    grid = store.load_xyz_grid (os.path.join (dem_dir, file_name))
    parts.append(grid)

whole_dem = Raster.merge(parts)

hillshader = IgorHillshader()

hillshadingBitmap = HillShadingProcessor.shade(whole_dem, hillshader, Color("black"), 1)
hillshadingBitmap.save("dem.png")

Spatial References

One of the biggest pieces of coding I had to do was to introduce SRIDs into the picture. This allows consuming of data sources which use spatial reference systems different from the WGS 84 lon/lat used by OSM. The next step will be to actually support different SRS-es, including different coordinate systems and different map projections.

When?

This is all very nice, but when will you get to use it? Well, maperipy needs a lot more work (what I’ve shown here is just a start!) and so does the main Maperitive code. I also need to write some documentation about the API. So my guesstimate would be a couple of months. Given that a lot of new functionality has been introduced since the last “official” release, I’m thinking of naming the next major release as “Maperitive 2.0” and this will include all the stuff introduced in the September beta release.

September 13, 2011
maperipy: Maperitive API for Python - A First Glimpse

A few days ago I started implementing maperipy, a Maperitive API for Python. It turns out to be a quite uncomplicated thing to do, compared to all infrastructure stuff I’m working on for the next Maperitive release, so it’s a kind of a relaxation between all the hard work.

The API is still very young and unsophisticated, but I’m very excited about it, because it could provide a whole new dimension of functionality to Maperitive users.

Here’s a simple Python script that clears the map and then adds a custom Web map to it (with custom copyright text):

from maperipy import MapLayer

map.clear_map();
layer = map.add_web_map_custom(
        "my hiking map", 
        ["http://beta1234.com.sunflower.arvixe.com/maps/tiles"])
layer.copyright = "it's made by me"
layer.opacity = 0.75

Another one, which scrolls the map along the X-axis:

from maperipy import MapPosition
import time

pos = map.position;

for n in range(10):
    map.position = MapPosition(15 + n/10.0, pos.y, 11)
    app.refresh_map()
    time.sleep(0.3)

First versions of maperipy will probably be available after I finish the work on the next major release of Maperitive. Hopefully some time in October.

April 2, 2011
Maperitive: Python Scripting Introduction

The upcoming release of Maperitive comes with a new and exciting feature: Python scripting.

What I’ll describe here is just an initial introduction of Python into Maperitive and right now is more of an experimental nature. I have great plans with Python, but there is still a lot of work to do and I wanted to release something to users so they can test it out and give some feedback.

Writing Python Functions

In the new release Python scripting can be used to define text labels. Let me give you an example with a piece of Python code:

def cycleLabel(e):
    str_list = []
    for set in e.tagSets:
        if set.hasTag('ref'):
            str_list.append(set['ref'])

    str_list.sort()
    return '+'.join(str_list)

So what does this code do? I won’t go into Python syntax (since I think it’s not that hard to understand for anyone with a little bit of programming inclination), but I’ll explain some basics.

We wrote a nice little function called cycleLabel which receives a map element (e) as an input argument. The function goes through the list of element tags and looks for ref tags, which it then puts into a list. The list is then sorted and transformed into a single string using the plus sign as a delimiter.

The above function can be used to render cycle route names. In OSM these are usually represented using cycle relations. Each route has its own relation, but several routes can share the same OSM way, so we need a way to show all the route names in a single label for that way. This is where the cycleLabel function comes into play.

A name of a route is stored in the ref tag and we collect all ref tags to be able to show them as a single label:

Using Python Code

Once we have our labeling function, we need to tell Maperitive to use it. Here’s an excerpt of rendering rules I used to produce the above map:

import-script:test.py

features
    lines
        cycle route : osmnetwork[type=route]
    ...     
rules
    target : cycle route
        define
            line-width : 3
            line-color : red
        draw : line
        define
            text-func : cycleLabel(e)
        draw : text

The important things are:

  • import-script tells Maperitive to load a Python source file
  • text-func is a new rendering property which you can use to call a Python function to prepare the text label.

In the next post we’ll go through element variable attributes which can be used in Python code to access various properties of the element.

March 18, 2011
Python’s try-except-else-finally Block

Started reading IronPython in Action to get acquainted with my language of choice for Maperitive scripting.

While browsing through the book, I noticed a little gem I complained was missing in C# a few days ago (code stolen from here):

try:
    f = open(arg, 'r')
except IOError:
    print 'cannot open', arg
else:
    print arg, 'has', len(f.readlines()), 'lines'
    f.close()

And the explanation for the else clause:

The use of the else clause is better than adding additional code to the try clause because it avoids accidentally catching an exception that wasn’t raised by the code being protected by the try … except statement.

So it looks like I’m not that crazy afterall…

Liked posts on Tumblr: More liked posts »