class Legend(models.Model):
    map = models.OneToOneField('Map')

    def __unicode__(self):
        return 'ID: %i,Map: %s' % (self.id, self.map)

    def create(self):
        for layer in self.map.layers.all().order_by('maplayer__layer_order'):
            for style in layer.styles.all():
                for rule in style.rules.all().order_by('rulestyle__order'):
                    if rule.name:
                        for zoom in range(rule.maxscale, rule.minscale + 1):
                            l = LegendItem()
                            l.save_legend(self, rule, zoom)

    def create_images(self, zoom, scale_factor=1):
        for item in self.legenditem_set.select_related().filter(zoom=zoom):
            if item.legend_item_name.name:
                item.create_image(scale_factor)

    def create_all_images(self, scale_factor=1):
        for zoom in range(0, 19):
            self.create_images(zoom, scale_factor)

    @staticmethod
    def create_all_name_images(font_size=12, scale_factor=1):
        for lin in LegendItemName.objects.all():
            lin.render_names(font_size, scale_factor)

    def legend_items(self, zoom):
        return (self.legenditem_set.select_related().filter(zoom=zoom).exclude(image='')
                .order_by('legend_item_name__group', 'legend_item_name__order', 'legend_item_name__slug')) def name_image(name, width, highres=True):
    height = 40
    font_size = 24
    if highres:
        # width is doubled in exportmap()
        height *= 2
        font_size *= 2
    svg = ''
    svg += '<?xml version="1.0" encoding="UTF-8"?>\n'
    svg += ('<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" '
            '"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">\n')
    svg += ('<svg width="%i" height="%i" xmlns="http://www.w3.org/2000/svg" id="name">\n' % (width, height))
    svg += '    <rect x="0" y="0" width="%i" height="%i" style="fill:white;stroke:none" />\n' % (width, height)
    svg += ('    <text fill="black" text-anchor="middle" font-family="Dejavu Sans" x="%i" y="%i"'
            ' font-size="%i">%s</text>' % (width/2, height-10, font_size, name))
    # print SVG end element
    svg += '</svg>'
    im = svg_string_to_png(svg)
    return im def map_image(zoom, left, bottom, right, top, line, orientation='n', highres=True):
    mapfile = settings.MAPNIK_STYLES + "mapnik2normal.xml"
    if orientation != 'n':
        mapfile = settings.MAPNIK_STYLES + "mapnik2orlice_%s.xml" % str(orientation)
    imgx, imgy = get_image_size(zoom, top, left, bottom, right)
    if orientation in ('w', 'e'):
        imgx, imgy = imgy, imgx
    if highres:
        mapfile = settings.MAPNIK_STYLES + "mapnik2print.xml"
        if orientation != 'n':
            mapfile = settings.MAPNIK_STYLES + "mapnik2print_orlice_%s.xml" % str(orientation)
        imgx = 2*imgx
        imgy = 2*imgy

    m = mapnik.Map(imgx, imgy)
    mapnik.load_map(m, mapfile)
    prj = mapnik.Projection('+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0'
                            ' +k=1.0 +units=m +nadgrids=@null +no_defs +over')
    # North on top
    n0 = prj.forward(mapnik.Coord(left, bottom))
    n1 = prj.forward(mapnik.Coord(right, top))
    # East on top
    e0 = mapnik.Coord(-n0.y, n0.x)
    e1 = mapnik.Coord(-n1.y, n1.x)
    # South on top
    s0 = mapnik.Coord(-n1.x, -n1.y)
    s1 = mapnik.Coord(-n0.x, -n0.y)
    # West on top
    w0 = mapnik.Coord(-e1.x, -e1.y)
    w1 = mapnik.Coord(-e0.x, -e0.y)
#    bottom_left = prj.forward(mapnik.Coord(left, bottom))
#    top_right = prj.forward(mapnik.Coord(right, top))
    boxes = {'n': mapnik.Box2d(n0.x, n0.y, n1.x, n1.y),
             'e': mapnik.Box2d(e0.x, e0.y, e1.x, e1.y),
             's': mapnik.Box2d(s0.x, s0.y, s1.x, s1.y),
             'w': mapnik.Box2d(w0.x, w0.y, w1.x, w1.y)
             }

    if line:
        gpxstyle = mapnik.Style()
        gpxrule = mapnik.Rule()
        lns = mapnik.LineSymbolizer()
        stroke = mapnik.Stroke()
        stroke.color = mapnik.Color('#FF6600')
        if highres:
            stroke.width = 10
        else:
            stroke.width = 5
        stroke.opacity = 0.9
        stroke.line_join = mapnik.line_join.names['round']
        stroke.line_cap = mapnik.line_cap.names['round']
        lns.stroke = stroke
        gpxrule.symbols.append(lns)
        gpxstyle.rules.append(gpxrule)
        m.append_style('gpxstyle', gpxstyle)
        gpxlayer = mapnik.Layer('gpx')
        gpxlayer.datasource = mapnik.Ogr(file=line, layer='OGRGeoJSON')
        gpxlayer.styles.append('gpxstyle')
        m.layers.append(gpxlayer)

    bbox = boxes[orientation]
    m.zoom_to_box(bbox)
    im = mapnik.Image(imgx, imgy)
    mapnik.render(m, im)
    return Image.open(StringIO(im.tostring('png'))) def legend_image(legend, zoom, gap, position='side', max_edge=None, highres=True):
    items = legend.legend_items(zoom).select_related()
    if not items.count():
        # no legend items for this zoom, return empty Image
        return Image.new('RGBA', (0, 0), 'white')
    if highres:
        gap *= 2
        params = items.aggregate(Max('width_highres'), Max('legend_item_name__width_highres'))
        max_image_width = params['width_highres__max'] + 2*gap
        max_name_width = params['legend_item_name__width_highres__max']
    else:
        params = items.aggregate(Max('width'), Max('legend_item_name__width'))
        max_image_width = params['width__max'] + 2*gap
        max_name_width = params['legend_item_name__width__max']
    column_width = max_image_width + max_name_width

    max_height = gap*(len(items) + 1)
    if highres:
        for item in items:
            max_height += max(item.legend_item_name.height_highres, item.height_highres)
    else:
        for item in items:
            max_height += max(item.legend_item_name.height, item.height)
    height = max_height
    num_columns = 1

    if position == 'side' and max_height > max_edge:
        num_columns = max_height / max_edge + 1
        height = max_edge
    elif position == 'bottom' and max_edge > column_width:
        num_columns = max_edge / column_width
        height = int(1.05 * max_height / num_columns)

    width = column_width * num_columns + gap
    image = mapnik.Image(width, height)
    image.background = mapnik.Color('white')
    y = gap
    column = 0
    for item in items:
        if highres:
            image_height = item.height_highres
            name_height = item.legend_item_name.height_highres
            image_width = item.width_highres
            path = item.image_highres.path.encode('utf-8')
            name_path = item.legend_item_name.image_highres.path.encode('utf-8')
        else:
            image_height = item.height
            name_height = item.legend_item_name.height
            image_width = item.width
            path = item.image.path.encode('utf-8')
            name_path = item.legend_item_name.image.path.encode('utf-8')
        shift = max(name_height, image_height)
        name_y = y
        image_y = y
        if image_height > name_height:
            name_y = y + (image_height - name_height) / 2
        else:
            image_y = y + (name_height - image_height) / 2
        image.blend(column * column_width + max_image_width / 2 - image_width / 2, image_y, mapnik.Image.open(path), 1)
        image.blend(column * column_width + max_image_width, name_y, mapnik.Image.open(name_path), 1)
        y = y + shift + gap
        if y + shift > height:
            y = gap
            column += 1
    return Image.open(StringIO(image.tostring('png'))) def scalebar_image(zoom, lat_center, highres=True):
    zoom_conversion = BASE_RANGE*2**(18-zoom)
    lat_conversion = cos(radians(lat_center))
    # pixel length at given latitude and zoom in real world in meters
    pixel = DEGREE_M*zoom_conversion*lat_conversion

    font_size = 12
    x_start = 10
    height = 40
    scale_line_length = 200
    real_line_length = scale_line_length * pixel  # real distance
    line_width = 1
    if real_line_length > 1000:
        #scale in kilometers
        units = 'km'
    else:
        #scale in meters
        units = 'm'
    real_line_length = int(round(real_line_length, -int(log10(real_line_length))))

    if highres:
        font_size *= 2
        x_start *= 2
        height *= 2
        scale_line_length *= 2
        line_width *= 2

    width = scale_line_length + 4*x_start
    x_middle = scale_line_length/2 + x_start
    x_end = scale_line_length + x_start
    y_top = height - x_start
    y_bottom = height - x_start/2
    y_text = y_top - x_start
    line_style = ('stroke:black; stroke-width:%ipx; stroke-linecap:butt; stroke-linejoin:miter;'
                  ' stroke-opacity:1' % line_width)
    svg = ''
    if units == 'km':
        real_line_length *= 0.001
    # write SVG headers
    svg += '<?xml version="1.0" encoding="UTF-8"?>\n'
    svg += ('<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN"'
            ' "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">\n')
    svg += '<svg width="%i" height="%i" xmlns="http://www.w3.org/2000/svg" id="scalebar">\n' % (width, height)
    svg += '    <rect x="0" y="0" width="%i" height="%i" style="fill:white;stroke:none" />\n' % (width, height)
    # draw altitude fields
    svg += '    <line style="%s" x1="%i" x2="%i" y1="%i" y2="%i" />\n' % (line_style, x_start,
                                                                          x_end, y_bottom, y_bottom)
    svg += '    <line style="%s" x1="%i" x2="%i" y1="%i" y2="%i" />\n' % (line_style, x_start,
                                                                          x_start, y_bottom, y_top)
    svg += '    <line style="%s" x1="%i" x2="%i" y1="%i" y2="%i" />\n' % (line_style, x_middle,
                                                                          x_middle, y_bottom, y_top)
    svg += '    <line style="%s" x1="%i" x2="%i" y1="%i" y2="%i" />\n' % (line_style, x_end,
                                                                          x_end, y_bottom, y_top)
    svg += ('    <text fill="black" text-anchor="middle" font-size="%i" font-family="Dejavu Sans"'
            ' x="%i" y="%i">0</text>\n' % (font_size, x_start, y_text))
    if real_line_length % 2:
        svg += ('    <text fill="black" text-anchor="middle" font-size="%i" font-family="Dejavu Sans" x="%i"'
                ' y="%i">%s</text>\n' % (font_size, x_middle, y_text, real_line_length/2.0))
    else:
        svg += ('    <text fill="black" text-anchor="middle" font-size="%i" font-family="Dejavu Sans" x="%i"'
                ' y="%i">%i</text>\n' % (font_size, x_middle, y_text, real_line_length/2))
    svg += ('    <text fill="black" text-anchor="end" font-size="%i" font-family="Dejavu Sans" x="%i"'
            ' y="%i">%s</text>\n' % (font_size, x_end + 4, y_text, int(real_line_length)))
    svg += ('    <text fill="black" text-anchor="start" font-size="%i" font-family="Dejavu Sans" x="%i" dx="0"'
            ' y="%i">%s</text>\n' % (font_size, x_end + 6, y_text, units))
    # print SVG end element
    svg += '</svg>\n'

    png_scalebar = svg_string_to_png(svg)
    return png_scalebar def imprint_image(attribution='Data: OpenStreetMap, CC-BY-SA', width=500, height=40, font_size=20, highres=True):
    if highres:
        # width is doubled in exportmap()
        height *= 2
        font_size *= 2
    today = date.today().strftime('%d. %m. %Y')
    text = (u'%s: Martin Tesař | %s: %s | %s: %s | %s: %s | www.mtbmap.cz' % (_('Author'), _('Projection'),
            _('Conformal cylindrical - Mercator'), _('Data'), attribution, _('Created'), today))
    svg = '<?xml version="1.0" encoding="UTF-8"?>\n'
    svg += '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN"'
    svg += ' "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">\n'
    svg += '<svg width="%i" height="%i" xmlns="http://www.w3.org/2000/svg" id="scalebar">\n' % (width, height)
    svg += '    <rect x="0" y="0" width="%i" height="%i" style="fill:white;stroke:none" />\n' % (width, height)
    svg += ('    <text fill="black" text-anchor="middle" font-family="Dejavu Sans" font-size="%i"'
            ' x="%i" y="%i">%s</text>\n' % (font_size, width/2, height-font_size/2, text))
    svg += '</svg>\n'

    png_imprint = svg_string_to_png(svg)
    return png_imprint def get_image_size(zoom, top, left, bottom, right):
    base = 0.000005364418029785156  # longitude range of 1 pixel at zoom 18
    zoom_conversion = base*2**(18-zoom)
    imgx = int(round((right - left)/zoom_conversion))
    lat_center = (top + bottom)/2
    latitude_conversion = zoom_conversion * cos(radians(lat_center))
    imgy = int(round((top - bottom)/latitude_conversion))
    return imgx, imgy class AltitudeProfile:
    def __init__(self, points):
        self.input_nodes = [ProfileNode(point[0], point[1]) for point in points]
        self.sumdist = self.compute_distances(self.input_nodes)
        self.nodes = self._insert_nodes(self.input_nodes)
        self.status = self._initialize_hgt_files()
        self.status -= self._compute_heights()

    def svg_profile(self):
        """
        Draws svg file from given nodes.
        """
        if self.status != 0:
            return NONE_HEIGHT
        svg = ''
        # write SVG headers
        svg += '<?xml version="1.0" encoding="UTF-8"?>\n'
        svg += '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN"'
        svg += ' "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">\n'
        svg += '<svg width="1010" height="300" viewBox="0 0 1010 300" xmlns="http://www.w3.org/2000/svg"'
        svg += ' xmlns:xlink="http://www.w3.org/1999/xlink">\n'
        # draw altitude fields
        svg += '    <rect x="0" y="0" width="1010" height="300" fill="white" />\n'
        svg += '    <rect x="35" y="30" width="950" height="246" fill="none" stroke="black" stroke-width="1" />\n'
        max_height = -10000
        min_height = 10000
        # find min/max height
        for node in self.nodes:
            if node.height > max_height:
                max_height = node.height
            if node.height < min_height:
                min_height = node.height
        # avoids division by zero
        if max_height == min_height:
            max_height += 1
        # constants for mapping real distances and heights into the picture
        norm_y = float(max_height - min_height) / 200
        norm_x = self.sumdist / 950
        # begin drawing polyline
        svg += '    <path fill="none" stroke="black" stroke-width="2" stroke-linecap="round"'
        svg += ' stroke-linejoin="round" d="M '
        x = 35
        difference = 0
        for node in self.nodes:
            # xf is the shift on x-axis, diff is the remainder after rounding
            xf = (node.dist/norm_x) + difference
            difference = xf - round(xf)
            x = x + round(xf)
            y = 255 - round((node.height-min_height) / norm_y)
            svg += str(int(x)) + " " + str(int(y)) + " L "
        svg += str(int(x)) + " " + str(int(y))
        max_y = int(255 - round(((max_height-min_height) / norm_y)))
        # finish drawing polyline
        svg += '"/>\n'

        # print height lines
        svg += '    <text fill="black" font-family="sans" font-size="12" text-anchor="end" x="30" y="256">' + str(min_height) + '</text>\n'
        svg += '    <line stroke = "red" stroke-dasharray="2,2" x1="35" y1="255" x2="985" y2="255"/>\n'
        svg += '    <text fill="black" font-family="sans" font-size="12" text-anchor="end" x="30"'
        svg += ' y="' + str(max_y + 4) + '">' + str(max_height) + '</text>\n'
        svg += '    <line stroke = "red" stroke-dasharray="2,2"'
        svg += ' x1="35" y1="' + str(max_y) + '" x2="985" y2="' + str(max_y) + '"/>\n'
        svg += '    <text fill="black" font-family="sans" font-size="12" text-anchor="middle" x="985"'
        svg += ' y="288">' + str(round(self.sumdist, 1)) + ' km</text>\n'
        # assign 'h' max_height floored to hundreds
        h = (max_height/100) * 100
        # avoid label collisions
        if (max_height-h) < (max_height-min_height)/20.0:
            h -= 100
        while h > (min_height + (max_height-min_height)/10):  # condition avoids label collisions
            h_coord = int(255-round((h-min_height)/norm_y))
            svg += '    <line stroke = "black" stroke-dasharray="2,2"'
            svg += ' x1="35" y1="' + str(h_coord) + '" x2="985" y2="' + str(h_coord) + '"/>\n'
            svg += '    <text fill="black" font-family="sans" font-size="12" text-anchor="end" x="30"'
            svg += ' y="' + str(h_coord + 4) + '">' + str(h) + '</text>\n'
            h -= 100
        # print distance markers, +/- 5 markers
        if self.sumdist > 25:
            step = int(round(self.sumdist/50)*10)
        elif self.sumdist > 2.5:
            step = int(round(self.sumdist/5))
        else:
            step = 0.2
        dist = step
        svg += '    <text fill="black" font-family="sans" font-size="12" text-anchor="middle" x="35" y="288">0</text>\n'
        while dist < self.sumdist - self.sumdist/20:  # condition 'self.sumdist/20' avoids label collision
            svg += '    <line stroke ="black" x1="' + str(round(dist/norm_x) + 35) + '" y1="276"'
            svg += ' x2="' + str(round(dist/norm_x) + 35) + '" y2="269"/>\n'
            svg += '    <text fill="black" font-family="sans" font-size="12" text-anchor="middle" x="' + str(round(dist/norm_x) + 35)
            svg += '" y="288">' + str(dist) + '</text>\n'
            dist += step
        # print ascending and descending
        ascdesc = self.ascending()
        svg += '    <text fill="black" font-family="sans" font-size="12" text-anchor="middle" x="550" y="20">%s: %i %s: %i</text>\n' % (
            _('Ascending'), ascdesc[0],
            _('Descending'), ascdesc[1])
        svg += '    <text fill="black" font-family="sans" font-size="12" x="2" y="25">%s (m)</text>\n' % _('Height')
        # print SVG end element
        svg += '</svg>'
        return svg

    def png_profile(self):
        """
        Create PNG image from SVG file.
        """
        if self.status != 0:
            return NONE_HEIGHT
        svg = self.svg_profile()
        return svg_string_to_png(svg)

    @staticmethod
    def compute_distances(nodes):
        """
        Compute distance to previous node and sum of distances.
        """
        sumdist = 0
        nodes[0].dist = 0
        for i in range(len(nodes)-1):
            dist = haversine(nodes[i].lon, nodes[i].lat, nodes[i+1].lon, nodes[i+1].lat)
            sumdist += dist
            nodes[i+1].dist = dist
        return sumdist

    def _insert_nodes(self, nodes):
        """
        Adds some nodes if the distance between the given nodes is too long.
        """
        # Threshold defines which nodes should be divided.
        # If the overall distance is less than 50 km, threshold is 100m, because of
        # the granularity of height data.
        if self.sumdist < 50:
            threshold = 0.1
        else:
            threshold = self.sumdist/500
        analyzed_nodes = [nodes[0]]
        previous = nodes[0]
        for node in nodes[1:]:
            # if distance between current and previous node is greater than threshold
            if node.dist > threshold:
                # steps is the number of added nodes
                steps = int(math.floor(node.dist/threshold))
                dlat = (node.lat - previous.lat)/steps
                dlon = (node.lon - previous.lon)/steps
                # add nodes
                for step in range(steps):
                    newlat = analyzed_nodes[-1].lat + dlat
                    newlon = analyzed_nodes[-1].lon + dlon
                    new_node = ProfileNode(newlat, newlon)
                    new_node.dist = haversine(analyzed_nodes[-1].lon, analyzed_nodes[-1].lat, newlon, newlat)
                    analyzed_nodes.append(new_node)
                    index = len(analyzed_nodes) - 1
                    node.dist = node.dist - analyzed_nodes[index].dist
            analyzed_nodes.append(node)
            previous = node
        # return new list of nodes
        return analyzed_nodes

    def _initialize_hgt_files(self):
        """
        Open all height files needed just once.
        """
        hgt_files = {}
        for node in self.nodes:
            file_key = hgt_file_key(node.lat, node.lon)
            if not (file_key in hgt_files):
                try:
                    hgt_files[file_key] = HgtFile(node)
                except IOError:
                    return NONE_HEIGHT
            node.hgt_file = hgt_files[file_key]
        return 0

    def _compute_heights(self):
        """
        Compute height for all nodes. Missing data is interpolated based on
        nearest neighbor height data.
        """
        for i in range(len(self.nodes)):
            node = self.nodes[i]
            node.height = node.srtm_height()
            if (node.height == NONE_HEIGHT) and (i > 1):
                node.height = self.nodes[i - 1].height
            # Parsing missing height data
            if node.height == NONE_HEIGHT:
                j = 1
                while (self.nodes[j].height == NONE_HEIGHT) and j < len(self.nodes) - 1:
                    j += 1
                if j == len(self.nodes) - 1:
                    return NONE_HEIGHT
                while j > 0:
                    self.nodes[j - 1].height = self.nodes[j].height
                    j -= 1
        if self.nodes[0].height == NONE_HEIGHT:
            # First node has missing height, find first node with height and
            # copy this value to all previous nodes.
            j = 1
            while (self.nodes[j].height == NONE_HEIGHT) and j < len(self.nodes) - 1:
                j += 1
            if j == len(self.nodes) - 1:
                return NONE_HEIGHT
            while j > 0:
                self.nodes[j - 1].height = self.nodes[j].height
                j -= 1
        return 0

    def ascending(self):
        """
        Counts total ascending and descending.
        """
        asc = 0
        desc = 0
        for i in range(len(self.nodes) - 1):
            dif = self.nodes[i + 1].height - self.nodes[i].height
            if dif > 0:
                asc = asc + dif
            else:
                desc = desc - dif
        return [asc, desc] def height(point):
    """
    Get height of point with coordinates: (lat,lon)
    """
    hgt = ProfileNode(point[0], point[1]).srtm_height()
    if hgt < 0:
        return NONE_HEIGHT
    else:
        return hgt class MultiRoute:
    """
    Wrapper for route searching, contains all route points and connects
    results of route search.
    """
    def __init__(self, points, flat_params):
        self.params = RouteParams(flat_params)
        self.points = points
        self.length = 0
        self.cost = 0
        self.status = 'init'
        self.geojson_features = []
        self.route_points = self._create_route_points()

    def _create_route_points(self):
        return [RoutePoint(point, self.params) for point in self.points]

    def geojson(self):
        """
        Create GeoJSON like object of type FeatureCollection.
        """
        return {
            'type': 'FeatureCollection',
            'properties': {
                'status': self.status,
                'length': self.length
            },
            'features': self.geojson_features
        }

    def search_index(self):
        """
        Compute ratio between cost and real length of the road
        """
        if self.length > 0:
            return self.cost/self.length
        else:
            return -1

    def find_multiroute(self):
        """
        Find route between all points, according to given params.
        """
        start = datetime.now()
        for point in self.route_points:
            if point.status == 'notfound':
                self.status = 'notfound'
        if self.status != 'notfound':
            for i in range(len(self.route_points)-1):
                start_point = self.route_points[i]
                end_point = self.route_points[i+1]
                route = Route(start_point, end_point, self.params)
                route.find_best_route()
                if route.status == 'notfound':
                    self.status = route.status
                self.length += route.length
                self.cost += route.cost
                self.geojson_features += route.geojson()
        for point in self.route_points:
            point.delete_temp_ways()
        if self.status == 'init':
            self.status = 'success'
        end = datetime.now()
        logger.info('MultiRoute search duration: %s' % total_seconds(end - start))
        return self.status def line_string_to_points(line_string):
    """
    Parse line geometry, represented as a string.
    return array of GEOS Points
    """
    latlngs = [coord.strip().replace('LatLng(', '').replace(')', '')
               for coord in line_string.replace('[', '').replace(']', '').split(',')]
    point_strings = [latlngs[i+1] + ' ' + latlngs[i] for i in range(0, len(latlngs), 2)]
    return [GEOSGeometry('SRID=4326;POINT(%s)' % point) for point in point_strings] def create_gpx(points):
    """
    Simple creation of GPX XML string.
    """
    output = libxml2.parseDoc('<gpx/>')
    root_node = output.getRootElement()
    root_node.setProp('creator', 'http://mtbmap.cz/')
    root_node.setProp('version', '1.1')
    root_node.setProp('xmlns', 'http://www.topografix.com/GPX/1/1')
    root_node.setProp('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance')
    root_node.setProp('xsi:schemaLocation', 'http://www.topografix.com/GPX/1/1 gpx.xsd')
    trkseg_node = libxml2.newNode('trkseg')
    for point in points:
        rtept = libxml2.newNode('trkpt')
        rtept.setProp('lat', str(point[0]))
        rtept.setProp('lon', str(point[1]))
        trkseg_node.addChild(rtept)
    trk_node = libxml2.newNode('trk')
    trk_node.addChild(trkseg_node)
    root_node.addChild(trk_node)
    return output.serialize('utf-8', 1) class RouteParams:
    """
    Parameters and preferences of route search.
    """
    def __init__(self, flat_params):
        self.raw_params = self._recreate_params(flat_params)
        self.reverse = 'oneway' in self.raw_params['global']
        self.where = '(id IS NOT NULL)'
        self.cost = 'length'
        self.reverse_cost = 'reverse_cost'
        self.raw_params['preferred_classes'] = self._preferred_classes()
        self.weight_collection = WeightCollection.objects.get(pk=self.raw_params['weights']['template'])
        self.weight_collection.vehicle = self.raw_params['global']['vehicle']
        self._cost_and_where()

    def weighted_ways_astar(self):
        """
        Create sql query for pgRouting A Star.
        return sql query string
        """
        where = 'WHERE ' + self.where + " OR highway='temp'"
        if self.reverse:
            return ('SELECT id, source::int4, target::int4, %s AS cost, %s AS reverse_cost, x1, x2, y1, y2 '
                    'FROM routing_way %s' % (self.cost, self.reverse_cost, where))
        else:
            return ('SELECT id, source::int4, target::int4, %s AS cost, x1, x2, y1, y2 FROM routing_way %s'
                    % (self.cost, where))

    def weighted_ways_dijkstra(self):
        """
        Create sql query for pgRouting Dijkstra.
        return sql query string
        """
        where = "WHERE " + self.where + " OR highway='temp'"
        if self.reverse:
            return ('SELECT id, source::int4, target::int4, %s AS cost, %s AS reverse_cost FROM routing_way %s'
                    % (self.cost, self.reverse_cost, where))
        else:
            return 'SELECT id, source::int4, target::int4, %s AS cost FROM routing_way %s' % (self.cost, where)

    def sql_astar_buffer(self, line_buffer):
        old = self.where
        self.where = "(the_geom && ST_GeomFromText('%s')) AND (%s)" % (line_buffer, self.where)
        sql_astar = self.weighted_ways_astar()
        self.where = old
        return sql_astar

    def _cost_and_where(self):
        """
        Create cost column definition and where clause.
        """
        self.cost, self.reverse_cost, self.where = self.weight_collection.get_cost_where_clause(self.raw_params)

    def _preferred_classes(self):
        preferred_classes = []
        if 'preferred' in self.raw_params:
            preferred_classes += self.raw_params['preferred'].keys()
        return preferred_classes

    @staticmethod
    def _recreate_params(flat_params):
        """
        Given flat JSON like object, create better python dictionary.
        """
        new = {}
        for p in flat_params:
            weight_class, feature = p['name'].split('__')
            if not weight_class in new:
                new[weight_class] = {}
            new[weight_class][feature] = p['value']
        return new

    def dump_params(self):
        collection = WeightCollection.objects.get(slug='empty')
        return collection.dump_params(self.raw_params) class WeightCollection(models.Model):
    __metaclass__ = TransMeta

    VEHICLE_CHOICES = (
        ('foot', _('Foot')),
        ('bicycle', _('Bicycle')),
    )
    slug = models.SlugField(max_length=40, unique=True)
    name = models.CharField(verbose_name=_('name'), max_length=40)
    oneway = models.BooleanField(default=True)
    vehicle = models.CharField(max_length=40, default='bicycle', choices=VEHICLE_CHOICES)

    class Meta:
        translate = ('name', )

    def __unicode__(self):
        return u"%s" % self.slug

    def get_cost_where_clause(self, params):
        """
        Create cost column definition and where clause.
        Returns tuple (cost_clause, reverse_cost_clause, where_clause)
        """
        where = '(id IS NOT NULL)'
        cost = 'length'
        unpreferred_preferences = {1: [], 2: [], 3: [], 4: [], 5: []}
        preferred_preferences = {1: [], 2: [], 3: [], 4: []}
        reverse_cases = []
        # conditions for oneways
        if self.vehicle == 'bicycle':
            # bicycles are allowed to go in some oneways reversely
            reverse_cases += ["""WHEN (bicycle!='opposite' OR bicycle IS NULL)
                AND reverse_cost!=length THEN reverse_cost """]
        elif self.vehicle == 'foot':
            # consider oneway only on paths, tracks, steps and footways
            reverse_cases += ["""WHEN highway IN ('path', 'track', 'steps', 'footway')
                AND reverse_cost!=length THEN reverse_cost """]
        else:
            # probably car, never route on oneway=yes
            reverse_cases += ["""WHEN reverse_cost!=length THEN reverse_cost """]
        whereparts = []
        whereparts += self._access()
        preferred_slugs = params['preferred_classes']
        for wcv in self.weightclassvalue_set.select_related().all():
            slug = wcv.weight_class.slug
            if slug in params:
                unpref_dict, pref_dict = wcv.get_when_clauses(params[slug], preferred_slugs)
                for pref, value in pref_dict.iteritems():
                    preferred_preferences[pref] += value
                for pref, value in unpref_dict.iteritems():
                    unpreferred_preferences[pref] += value
                part = wcv.get_where_clauses(params[slug])
                if part:
                    whereparts.append(part)
        cases = self._create_cases(unpreferred_preferences, preferred_preferences, preferred_slugs)
        if cases:
            reverse_cases += cases
            cost = 'CASE %s ELSE "length" END' % (' '.join(cases))
        if whereparts:
            where = "(" + " AND ".join(whereparts) + ")"
        reverse_cost = 'CASE %s ELSE "length" END' % (' '.join(reverse_cases))
        return cost, reverse_cost, where

    @staticmethod
    def _create_cases(unpref, pref, preferred_slugs):
        """
        Create array of WHEN clauses.
        """
        pref_classes_condition = ' OR '.join([p + '>0' for p in preferred_slugs])
        cases = []
        for preference in range(4, 0, -1):
            if len(preferred_slugs) and preference > 1 and pref[preference-1]:
                pref_joined_conditions = ('(' + ' OR '.join(pref[preference-1]) + ')'
                                          ' AND (' + pref_classes_condition + ')')
                cases.append('WHEN %s THEN "length"*%s' % (pref_joined_conditions, WEIGHTS[max(preference-2, 0)]))
            if unpref[preference]:
                unpref_joined_conditions = ' OR '.join(unpref[preference])
                cases.append('WHEN %s THEN "length"*%s' % (unpref_joined_conditions, WEIGHTS[preference-1]))
        return cases

    def _access(self):
        """
        Create access clause according to given role (car, bike, pedestrian,...)
        """
        # TODO: add vehicle access control, needs vehicle column in database and model
        access_clauses = []
        default_access = """(access IS NULL OR access NOT IN ('no', 'private'))"""
        if self.vehicle in ('bicycle', 'foot'):
            access_restrictions = """((access IS NULL OR access NOT IN ('no', 'private')) OR
                (access IN ('no', 'private') AND %s IN ('yes', 'designated')))""" % self.vehicle
            vehicle_restrictions = """(%s IS NULL OR NOT %s IN ('no', 'private'))""" % (self.vehicle, self.vehicle)
            access_clauses += [access_restrictions, vehicle_restrictions]
        else:
            access_clauses.append(default_access)
        return access_clauses

    def dump_params(self, params):
        """
        Dump weight collection params as JSON like dictionary.
        """
        json_params = dict()
        json_params['slug'] = params['global'].get('slug', 'undefined')
        json_params['oneway'] = 'oneway' in params['global']
        json_params['vehicle'] = params['global'].get('vehicle', 'bicycle')
        json_params['preferred'] = []
        for pv in self.preferredvalue_set.select_related().all():
            pref_class = dict(slug=pv.preferred.slug, use=True)
            pref_class['value'] = pv.preferred.slug in params['preferred_classes']
            json_params['preferred'].append(pref_class)
        json_params['classes'] = []
        for wcv in self.weightclassvalue_set.select_related().all():
            slug = wcv.weight_class.slug
            weight_class = {"slug": slug, "visible": True}
            if (wcv.max is not None) and ('max' in params[slug]):
                weight_class['max'] = params[slug]['max']
            if (wcv.min is not None) and ('min' in params[slug]):
                weight_class['min'] = params[slug]['min']
            if wcv.weightvalue_set.count():
                weight_class['features'] = []
                for wv in wcv.weightvalue_set.all():
                    weight_slug = wv.weight.slug
                    weight = dict(slug=weight_slug, visible=True)
                    weight["value"] = params[slug].get(weight_slug, wv.preference)
                    weight_class['features'].append(weight)
            json_params['classes'].append(weight_class)
        return json_params
from PIL import Image
from simplejson import JSONDecodeError
from django.shortcuts import render_to_response
from django.template import RequestContext
from django.template.response import TemplateResponse
from django.http import HttpResponse, HttpResponseRedirect
from django.utils import translation
from django.utils.translation import ugettext as _
from django.contrib.gis.geos import Point, LineString
from django.conf import settings
from map.models import *
from styles.models import Legend
from map.printing import name_image, map_image, legend_image, scalebar_image, imprint_image, get_image_size
from map.altitude import AltitudeProfile, height
from routing.core import MultiRoute, line_string_to_points, create_gpx, RouteParams
from routing.models import WeightCollection
import logging
# -*- coding: utf-8 -*-

# Global imports

# Django imports

# Local imports

logger = logging.getLogger(__name__)


def index(request):
    """
    Main map page.
    """
    weight_collections = WeightCollection.objects.all()
    default_tile_layer = TileLayer.objects.get(slug='mtb-map')
    tile_layers = TileLayer.objects.all()
    geojson_layers = GeojsonLayer.objects.all()
    return render_to_response('map/map.html', {'default_tile_layer': default_tile_layer,
                                               'zoomRange': range(19),
                                               'tile_layers': tile_layers,
                                               'geojson_layers': geojson_layers,
                                               'weight_collections': weight_collections},
                              context_instance=RequestContext(request))


def legend(request):
    """
    Legend items for given zoom.
    """
    zoom = int(request.GET['zoom'])
    legenditems = Legend.objects.all()[0].legend_items(zoom)
    return TemplateResponse(request, 'map/legend.html', {'zoom': zoom, 'legenditems': legenditems})


def routingparams(request):
    """
    Route parameters and preferences template for given id.
    """
    try:
        template_id = request.GET['template_id']
        weight_collection = WeightCollection.objects.get(pk=template_id)
    except KeyError:
        # template not found
        weight_collection = WeightCollection.objects.all()[0]
    return TemplateResponse(request, 'map/routingparams.html', {'weight_collection': weight_collection})


def exportmap(request):
    """
    Export map, response is PNG image.
    """
    try:
        zoom = int(request.POST['export-zoom'])
        bounds = request.POST['export-bounds'].replace('(', '').replace(')', '')
    except KeyError:
        # no zoom posted
        return render_to_response('map/map.html', {},
                                  context_instance=RequestContext(request))
    else:
        map_title = request.POST['map-title']
        if map_title.startswith('orlice_'):
            try:
                side = map_title.replace('orlice_', '')[0]
                if side in ('n', 'e', 's', 'w'):
                    orientation = side
                else:
                    orientation = 'n'
            except IndexError:
                orientation = 'n'
        else:
            orientation = 'n'
        tile_layer = TileLayer.objects.get(slug='mtb-map')
        bottom = float(bounds.split(',')[1])
        top = float(bounds.split(',')[3])
        left = float(bounds.split(',')[0])
        right = float(bounds.split(',')[2])
        zoom = int(zoom)
        map_x, map_y = get_image_size(zoom, top, left, bottom, right)
        if map_x * map_y > 6000000:
            message = _('Sorry, requested image is too big. Try export of smaller area.')
            return render_to_response('error.html', {'message': message}, context_instance=RequestContext(request))
        gap = 5
        line = None
        if 'export-line-check' in request.POST:
            raw_line = request.POST['export-line']
            points = []
            if len(raw_line):
                for part in raw_line.split('),'):
                    latlng = part.replace('LatLng(', '').replace(')', '').split(',')
                    point = Point(float(latlng[1]), float(latlng[0]))
                    points.append(point)
                if len(points) > 1:
                    line = LineString(points).geojson

        highres = 'export-highres' in request.POST
        map_im = map_image(zoom, left, bottom, right, top, line, orientation, highres)

        if 'export-legend' in request.POST:
            legend_obj = Legend.objects.all()[0]

            legend_im = legend_image(legend_obj, zoom, gap, 'side', map_im.size[1], highres)