Example #1
0
    def kml_chunk(self, n, s, e, w):
        """
        Get the kml of a lat/lon bounded part of the study region, 
        with geometry simplified in proportion to the visible % of the region
        """

        bounds = Polygon(LinearRing([Point(w, n), Point(e, n), Point(e, s), Point(w, s), Point(w, n)]))
        bounds.set_srid(4326)
        center_lat = bounds.centroid.y # in 4326 because it is used only for setting up the subregion calls
        center_lon = bounds.centroid.x # in 4326 because it is used only for setting up the subregion calls
        bounds.transform(settings.GEOMETRY_DB_SRID)

        # all longitudinal width calcs should be done in GEOMETRY_DB_SRID - 4326 can fail across the date line
        zoom_width = (Point(bounds.extent[0], bounds.centroid.y)).distance(Point(bounds.extent[2], bounds.centroid.y))

        full_shape_width = (Point(self.geometry.extent[0], self.geometry.centroid.y)).distance(Point(self.geometry.extent[2], self.geometry.centroid.y))

        # The following simplify values can be tuned to your preference
        # minimum geometry simplify value (highest detail) = 50 (arbitrary, based on observation)
        # maximum geometry simplify value = 200 (arbitrary, based on observation)
        # value set by pecentage of study region width requested in this chunk
        min_simplify_val = 50.0
        max_simplify_val = 200.0
        simplify_factor = max(min_simplify_val, min(max_simplify_val, max_simplify_val * zoom_width / full_shape_width))

        transform_geom = self.geometry.simplify(simplify_factor, preserve_topology=True)
        transform_geom = transform_geom.intersection(bounds)
        transform_geom.transform(4326)

        # Debugging info
        #print zoom_width
        #print full_shape_width
        #print simplify_factor
        #print transform_geom.num_coords
        # End debugging info

        # only add sub-regions if this is not our highest detail level
        bLastLodLevel = simplify_factor < max_simplify_val # change this last value to build varying levels of LOD
        max_lod_pixels = 500
        min_lod_pixels = 250

        # make sure the most detailed lod stays active no matter how close user zooms
        if bLastLodLevel:
            max_lod_pixels = -1

        retval = '<Region><LatLonAltBox><north>%f</north><south>%f</south><east>%f</east><west>%f</west></LatLonAltBox><Lod><minLodPixels>%f</minLodPixels><maxLodPixels>%f</maxLodPixels><minFadeExtent>0</minFadeExtent><maxFadeExtent>0</maxFadeExtent></Lod></Region>' % (n, s, e, w, min_lod_pixels, max_lod_pixels) + '<Placemark> <name>Study Region Boundaries</name><Style> <LineStyle> <color>ff00ffff</color> <width>2</width> </LineStyle> <PolyStyle> <color>8000ffff</color> </PolyStyle></Style>%s</Placemark>' % (transform_geom.kml,)

        # conditionally add sub-regions
        if not bLastLodLevel:
            subregions = '<Folder><name>Study Region LODs</name>' + '<Folder><name>SE</name>' + self.kml_chunk(center_lat, s, e, center_lon) + '</Folder>'

            subregions = subregions + '<Folder><name>NE</name>' + self.kml_chunk(n, center_lat, e, center_lon) + '</Folder>'       

            subregions = subregions + '<Folder><name>SW</name>' + self.kml_chunk(center_lat, s, center_lon, w) + '</Folder>'    

            subregions = subregions + '<Folder><name>NW</name>' + self.kml_chunk(n, center_lat, center_lon, w) + '</Folder>'

            retval = retval + subregions + '</Folder>'

        return retval 
Example #2
0
    def setUp(self):
        # Make sure we're using the GB postcode functions here
        models.countries = countries
        utils.countries = countries

        self.postcode = models.Postcode.objects.create(
            postcode='SW1A1AA',
            location=Point(-0.141588, 51.501009)
        )

        self.generation = models.Generation.objects.create(
            active=True,
            description="Test generation",
        )

        self.type = models.Type.objects.create(
            code="TEST_TYPE",
            description="A test area",
        )

        self.area = models.Area.objects.create(
            name="Area",
            type=self.type,
            generation_low=self.generation,
            generation_high=self.generation,
        )

        polygon = Polygon(((-5, 50), (-5, 55), (1, 55), (1, 50), (-5, 50)), srid=4326)
        polygon.transform(settings.MAPIT_AREA_SRID)
        self.shape = models.Geometry.objects.create(
            area=self.area, polygon=polygon,
        )
Example #3
0
    def project_to_WGS84(self, ov_bbox, ov_crs, center=None):
        try:
            srid = int(ov_crs.split(':')[1])
            srid = 3857 if srid == 900913 else srid
            poly = Polygon(((ov_bbox[0], ov_bbox[1]), (ov_bbox[0], ov_bbox[3]),
                            (ov_bbox[2], ov_bbox[3]), (ov_bbox[2], ov_bbox[1]),
                            (ov_bbox[0], ov_bbox[1])),
                           srid=srid)
            if srid != 4326:
                gcoord = SpatialReference(4326)
                ycoord = SpatialReference(srid)
                trans = CoordTransform(ycoord, gcoord)
                poly.transform(trans)
            try:
                if not center:
                    center = {
                        "x": get_valid_number(poly.centroid.coords[0]),
                        "y": get_valid_number(poly.centroid.coords[1]),
                        "crs": "EPSG:4326"
                    }
                zoom = GoogleZoom().get_zoom(poly) + 1
            except Exception:
                center = (0, 0)
                zoom = 0
                tb = traceback.format_exc()
                logger.debug(tb)
        except Exception:
            tb = traceback.format_exc()
            logger.debug(tb)

        return (center, zoom)
Example #4
0
 def _get_shape(self, value):
     points = json.loads(value)
     # close ring and create Polygon
     polygon = Polygon(points+[points[0]])
     polygon.srid = SRID_WSG84
     polygon.transform(SRID_RD)
     return polygon
Example #5
0
    def setUp(self):
        self.generation = Generation.objects.create(
            active=True,
            description="Test generation",
        )

        self.area_type = Type.objects.create(
            code="BIG",
            description="A large test area",
        )

        self.name_type = NameType.objects.create(
            code='O',
            description='Ordnance Survey name type'
        )

        self.area = Area.objects.create(
            name="Big Area",
            type=self.area_type,
            generation_low=self.generation,
            generation_high=self.generation,
        )

        polygon = Polygon(((-5, 50), (-5, 55), (1, 55), (1, 50), (-5, 50)), srid=4326)
        polygon.transform(settings.MAPIT_AREA_SRID)
        self.geometry = self.area.polygons.create(polygon=polygon)
    def setUp(self):
        self.generation = Generation.objects.create(
            active=True,
            description="Test generation",
        )

        self.area_type = Type.objects.create(
            code="BIG",
            description="A large test area",
        )

        self.name_type = NameType.objects.create(
            code='O', description='Ordnance Survey name type')

        self.area = Area.objects.create(
            name="Big Area",
            type=self.area_type,
            generation_low=self.generation,
            generation_high=self.generation,
        )

        polygon = Polygon(((-5, 50), (-5, 55), (1, 55), (1, 50), (-5, 50)),
                          srid=4326)
        polygon.transform(settings.MAPIT_AREA_SRID)
        self.geometry = self.area.polygons.create(polygon=polygon)
Example #7
0
 def find(self,
          bbox=None,
          text=None,
          adm1=None,
          adm2=None,
          is_primary=True,
          threshold=0.5,
          srid=4326):
     qset = self.get_query_set().filter(is_primary=is_primary)
     if bbox:
         (minx, miny, maxx, maxy) = bbox
         bbox = Polygon(((minx, miny), (minx, maxy), (maxx, maxy),
                         (maxx, miny), (minx, miny)),
                        srid=srid)
         if srid != 4326: bbox.transform(4326)  # convert to lon/lat
         qset = qset.filter(geometry__bboverlaps=bbox)
     if text:
         self.set_threshold(threshold)
         # use the pg_trgm index
         qset = qset.extra(
             select={"similarity": "similarity(preferred_name, %s)"},
             select_params=[text],
             where=["preferred_name %% %s"],
             params=[text],
             order_by=["-similarity"])
     if adm1: qset = qset.filter(admin1__exact=adm1)
     if adm2: qset = qset.filter(admin2__exact=adm2)
     return qset
Example #8
0
    def setUp(self):
        # Make sure we're using the GB postcode functions here
        models.countries = countries
        utils.countries = countries

        self.postcode = models.Postcode.objects.create(postcode='SW1A1AA',
                                                       location=Point(
                                                           -0.141588,
                                                           51.501009))

        self.generation = models.Generation.objects.create(
            active=True,
            description="Test generation",
        )

        self.type = models.Type.objects.create(
            code="TEST_TYPE",
            description="A test area",
        )

        self.area = models.Area.objects.create(
            name="Area",
            type=self.type,
            generation_low=self.generation,
            generation_high=self.generation,
        )

        polygon = Polygon(((-5, 50), (-5, 55), (1, 55), (1, 50), (-5, 50)),
                          srid=4326)
        polygon.transform(settings.MAPIT_AREA_SRID)
        self.shape = models.Geometry.objects.create(
            area=self.area,
            polygon=polygon,
        )
Example #9
0
def add_anonymous_boundary(request):
    request_dict = json.loads(request.body)
    srid = request_dict.get('srid', 4326)
    polygon = Polygon(request_dict.get('polygon', []), srid=srid)
    if srid != 3857:
        polygon.transform(3857)
    b = Boundary.anonymous(polygon)
    b.save()
    return {'id': b.id}
Example #10
0
def add_anonymous_boundary(request):
    request_dict = json.loads(request.body)
    srid = request_dict.get('srid', 4326)
    polygon = Polygon(request_dict.get('polygon', []), srid=srid)
    if srid != 3857:
        polygon.transform(3857)
    b = Boundary.anonymous(polygon)
    b.save()
    return {'id': b.id}
Example #11
0
def _shape(points):
    if points[0] != points[-1]:
        points.append(points[0])
    try:
        poly = Polygon([Point(*p, srid=OSGB) for p in points], srid=OSGB)
        poly.transform(WGS)
        return poly
    except Exception, e:
        print points
        print e
        raise
 def get_zoom(ov_bbox, ov_crs):
     srid = int(ov_crs.split(':')[1])
     srid = 3857 if srid == 900913 else srid
     poly = Polygon(((ov_bbox[0], ov_bbox[1]), (ov_bbox[0], ov_bbox[3]),
                     (ov_bbox[2], ov_bbox[3]), (ov_bbox[2], ov_bbox[1]),
                     (ov_bbox[0], ov_bbox[1])),
                    srid=srid)
     gcoord = SpatialReference(4326)
     ycoord = SpatialReference(srid)
     trans = CoordTransform(ycoord, gcoord)
     poly.transform(trans)
     zoom = GoogleZoom().get_zoom(poly)
     return zoom
def import_paths(filename):
    reader = shapefile.Reader(filename)
    records = reader.iterRecords()
    shapes = reader.iterShapes()

    cells = {}
    for record, shape in zip(records, shapes):
        id, x, y = record
        point = Point(x, y, srid=IN_SRID)
        point.transform(OUT_SRID)
        poly = Polygon(map(tuple, shape.points), srid=IN_SRID)
        poly.transform(OUT_SRID)
        cells[id] = Cell(id, point, poly)
    return cells
Example #14
0
    def setUp(self):
        self.generation = Generation.objects.create(
            active=True,
            description="Test generation",
            )

        self.big_type = Type.objects.create(
            code="BIG",
            description="A large test area",
            )

        self.small_type = Type.objects.create(
            code="SML",
            description="A small test area",
            )

        self.big_area = Area.objects.create(
            name="Big Area",
            type=self.big_type,
            generation_low=self.generation,
            generation_high=self.generation,
            )

        polygon = Polygon(((-5, 50), (-5, 55), (1, 55), (1, 50), (-5, 50)), srid=4326)
        polygon.transform(settings.MAPIT_AREA_SRID)
        self.big_shape = Geometry.objects.create(
            area=self.big_area, polygon=polygon)

        self.small_area_1 = Area.objects.create(
            name="Small Area 1",
            type=self.small_type,
            generation_low=self.generation,
            generation_high=self.generation,
            )

        self.small_area_2 = Area.objects.create(
            name="Small Area 2",
            type=self.small_type,
            generation_low=self.generation,
            generation_high=self.generation,
            )

        polygon = Polygon(((-4, 51), (-4, 52), (-3, 52), (-3, 51), (-4, 51)), srid=4326)
        polygon.transform(settings.MAPIT_AREA_SRID)
        self.small_shape_1 = Geometry.objects.create(
            area=self.small_area_1, polygon=polygon)

        polygon = Polygon(((-3, 51), (-3, 52), (-2, 52), (-2, 51), (-3, 51)), srid=4326)
        polygon.transform(settings.MAPIT_AREA_SRID)
        self.small_shape_2 = Geometry.objects.create(
            area=self.small_area_2, polygon=polygon)
        polygon = Polygon(((-2, 53), (-2, 54), (-1, 54), (-1, 53), (-2, 53)), srid=4326)
        polygon.transform(settings.MAPIT_AREA_SRID)
        self.small_shape_3 = Geometry.objects.create(
            area=self.small_area_2, polygon=polygon)

        self.postcode = Postcode.objects.create(
            postcode='P', location=Point(-3.5, 51.5))
Example #15
0
 def find(self, bbox=None, text=None, adm1=None, adm2=None, is_primary=True, threshold=0.5, srid=4326):
     qset = self.get_query_set().filter(is_primary=is_primary)
     if bbox:
         (minx, miny, maxx, maxy) = bbox
         bbox = Polygon(((minx,miny),(minx,maxy),(maxx,maxy),(maxx,miny),(minx,miny)),srid=srid)
         if srid != 4326: bbox.transform(4326) # convert to lon/lat
         qset = qset.filter(geometry__bboverlaps=bbox)
     if text:
         self.set_threshold(threshold)
         # use the pg_trgm index
         qset = qset.extra(select={"similarity":"similarity(preferred_name, %s)"},
                           select_params=[text],
                           where=["preferred_name %% %s"],
                           params=[text],
                           order_by=["-similarity"])
     if adm1: qset = qset.filter(admin1__exact=adm1)
     if adm2: qset = qset.filter(admin2__exact=adm2)
     return qset
Example #16
0
    def test_should_find_correct_parent(self):
        generation = models.Generation.objects.create(active=False, description="Test generation")
        parent_type = models.Type.objects.create(code="UTA", description="A parent test area")
        child_type = models.Type.objects.create(code="UTW", description="A child test area")
        parent_area_1 = models.Area.objects.create(
            name="Parent Area 1", type=parent_type, generation_low=generation, generation_high=generation)
        parent_area_2 = models.Area.objects.create(
            name="Parent Area 2", type=parent_type, generation_low=generation, generation_high=generation)
        child_area_1 = models.Area.objects.create(
            name="Child Area 1", type=child_type, generation_low=generation, generation_high=generation)
        child_area_2 = models.Area.objects.create(
            name="Child Area 2", type=child_type, generation_low=generation, generation_high=generation)

        polygon = Polygon(((-5, 50), (-5, 55), (1, 55), (1, 50), (-5, 50)), srid=4326)
        polygon.transform(settings.MAPIT_AREA_SRID)
        models.Geometry.objects.create(area=parent_area_1, polygon=polygon)

        polygon = Polygon(((1, 50), (1, 55), (5, 55), (5, 50), (1, 50)), srid=4326)
        polygon.transform(settings.MAPIT_AREA_SRID)
        models.Geometry.objects.create(area=parent_area_2, polygon=polygon)

        polygon = Polygon(((-4, 51), (-4, 52), (-3, 52), (-3, 51), (-4, 51)), srid=4326)
        polygon.transform(settings.MAPIT_AREA_SRID)
        models.Geometry.objects.create(area=child_area_1, polygon=polygon)

        polygon = Polygon(((2, 53), (2, 54), (3, 54), (3, 53), (2, 53)), srid=4326)
        polygon.transform(settings.MAPIT_AREA_SRID)
        models.Geometry.objects.create(area=child_area_2, polygon=polygon)

        stdout = StringIO()
        call_command(
            'mapit_UK_find_parents',
            commit=True,
            stderr=StringIO(),
            stdout=stdout,
        )

        child_area_1 = models.Area.objects.get(pk=child_area_1.id)
        child_area_2 = models.Area.objects.get(pk=child_area_2.id)

        expected = 'Parent for Child Area 1 [%d] (UTW) was None, is now Parent Area 1 [%d] (UTA)\n' % (
            child_area_1.id, parent_area_1.id)
        expected += 'Parent for Child Area 2 [%d] (UTW) was None, is now Parent Area 2 [%d] (UTA)\n' % (
            child_area_2.id, parent_area_2.id)
        self.assertEqual(stdout.getvalue(), expected)

        self.assertEqual(child_area_1.parent_area, parent_area_1)
        self.assertEqual(child_area_2.parent_area, parent_area_2)
Example #17
0
    def setUp(self):
        self.generation = Generation.objects.create(
            active=True,
            description="Test generation",
        )

        self.big_type = Type.objects.create(
            code="BIG",
            description="A large test area",
        )

        self.small_type = Type.objects.create(
            code="SML",
            description="A small test area",
        )

        self.big_area = Area.objects.create(
            name="Big Area",
            type=self.big_type,
            generation_low=self.generation,
            generation_high=self.generation,
        )

        polygon = Polygon(((-5, 50), (-5, 55), (1, 55), (1, 50), (-5, 50)),
                          srid=4326)
        polygon.transform(settings.MAPIT_AREA_SRID)
        self.big_shape = Geometry.objects.create(area=self.big_area,
                                                 polygon=polygon)

        self.small_area_1 = Area.objects.create(
            name="Small Area 1",
            type=self.small_type,
            generation_low=self.generation,
            generation_high=self.generation,
        )

        self.small_area_2 = Area.objects.create(
            name="Small Area 2",
            type=self.small_type,
            generation_low=self.generation,
            generation_high=self.generation,
        )

        polygon = Polygon(((-4, 51), (-4, 52), (-3, 52), (-3, 51), (-4, 51)),
                          srid=4326)
        polygon.transform(settings.MAPIT_AREA_SRID)
        self.small_shape_1 = Geometry.objects.create(area=self.small_area_1,
                                                     polygon=polygon)

        polygon = Polygon(((-3, 51), (-3, 52), (-2, 52), (-2, 51), (-3, 51)),
                          srid=4326)
        polygon.transform(settings.MAPIT_AREA_SRID)
        self.small_shape_2 = Geometry.objects.create(area=self.small_area_2,
                                                     polygon=polygon)

        self.postcode = Postcode.objects.create(postcode='P',
                                                location=Point(-3.5, 51.5))
Example #18
0
def map_tile(request, layer_slug, boundary_slug, tile_zoom, tile_x, tile_y, format):
    if not has_imaging_library: raise Http404("Cairo is not available.")
    
    layer = get_object_or_404(MapLayer, slug=layer_slug)
    
    # Load basic parameters.
    try:
        size = int(request.GET.get('size', '256' if format not in ('json', 'jsonp') else '64'))
        if size not in (64, 128, 256, 512, 1024): raise ValueError()
        
        srs = int(request.GET.get('srs', '3857'))
    except ValueError:
        raise Http404("Invalid parameter.")
        
    db_srs, out_srs = get_srs(srs)
    
    # Get the bounding box for the tile, in the SRS of the output.
    
    try:
        tile_x = int(tile_x)
        tile_y = int(tile_y)
        tile_zoom = int(tile_zoom)
    except ValueError:
        raise Http404("Invalid parameter.")
    
    # Guess the world size. We need to know the size of the world in
    # order to locate the bounding box of any viewport at zoom levels
    # greater than zero.
    if "radius" not in request.GET:
        p = Point( (-90.0, 0.0), srid=db_srs.srid )
        p.transform(out_srs)
        world_left = p[0]*2
        world_top = -world_left
        world_size = -p[0] * 4.0
    else:
        p = Point((0,0), srid=out_srs.srid )
        p.transform(db_srs)
        p1 = Point([p[0] + 1.0, p[1] + 1.0], srid=db_srs.srid)
        p.transform(out_srs)
        p1.transform(out_srs)
        world_size = math.sqrt(abs(p1[0]-p[0])*abs(p1[1]-p[1])) * float(request.GET.get('radius', '50'))
        world_left = p[0] - world_size/2.0
        world_top = p[1] + world_size/2.0
    tile_world_size = world_size / math.pow(2.0, tile_zoom)

    p1 = Point( (world_left + tile_world_size*tile_x, world_top - tile_world_size*tile_y) )
    p2 = Point( (world_left + tile_world_size*(tile_x+1), world_top - tile_world_size*(tile_y+1)) )
    bbox = Polygon( ((p1[0], p1[1]),(p2[0], p1[1]),(p2[0], p2[1]),(p1[0], p2[1]),(p1[0], p1[1])), srid=out_srs.srid )
    
    # A function to convert world coordinates in the output SRS into
    # pixel coordinates.
       
    blon1, blat1, blon2, blat2 = bbox.extent
    bx = float(size)/(blon2-blon1)
    by = float(size)/(blat2-blat1)
    def viewport(coord):
        # Convert the world coordinates to image coordinates according to the bounding box
        # (in output SRS).
        return float(coord[0] - blon1)*bx, (size-1) - float(coord[1] - blat1)*by

    # Convert the bounding box to the database SRS.

    db_bbox = bbox.transform(db_srs, clone=True)
    
    # What is the width of a pixel in the database SRS? If it is smaller than
    # SIMPLE_SHAPE_TOLERANCE, load the simplified geometry from the database.
    
    shape_field = 'shape'
    pixel_width = (db_bbox.extent[2]-db_bbox.extent[0]) / size / 2
    if pixel_width > boundaries_settings.SIMPLE_SHAPE_TOLERANCE:
        shape_field = 'simple_shape'

    # Query for any boundaries that intersect the bounding box.
    
    boundaries = Boundary.objects.filter(set=layer.boundaryset, shape__intersects=db_bbox)\
        .values("id", "slug", "name", "label_point", shape_field)
    if boundary_slug: boundaries = boundaries.filter(slug=boundary_slug)
    boundary_id_map = dict( (b["id"], b) for b in boundaries )
    
    if len(boundaries) == 0:
        if format == "svg":
            raise Http404("No boundaries here.")
        elif format in ("png", "gif"):
            # Send a 1x1 transparent image. Google is OK getting 404s for map tile images
            # but OpenLayers isn't. Maybe cache the image?
            im = cairo.ImageSurface(cairo.FORMAT_ARGB32, 1, 1)
            ctx = cairo.Context(im)
            buf = StringIO()
            im.write_to_png(buf)
            v = buf.getvalue()
            if format == "gif": v = convert_png_to_gif(v)
            r = HttpResponse(v, content_type='image/' + format)
            r["Content-Length"] = len(v)
            return r
        elif format == "json":
            # Send an empty "UTF-8 Grid"-like response.
            return HttpResponse('{"error":"nothing-here"}', content_type="application/json")
        elif format == "jsonp":
            # Send an empty "UTF-8 Grid"-like response.
            return HttpResponse(request.GET.get("callback", "callback") +  '({"error":"nothing-here"})', content_type="text/javascript")
    
    # Query for layer style information and then set it on the boundary objects.
    
    styles = layer.boundaries.filter(boundary__in=boundary_id_map.keys())
    for style in styles:
        boundary_id_map[style.boundary_id]["style"] = style
    
    # Create the image buffer.
    if format in ('png', 'gif'):
        im = cairo.ImageSurface(cairo.FORMAT_ARGB32, size, size)
    elif format == 'svg':
        buf = StringIO()
        im = cairo.SVGSurface(buf, size, size)
    elif format in ('json', 'jsonp'):
        # This is going to be a "UTF-8 Grid"-like response, but we generate that
        # info by first creating an actual image, with colors coded by index to
        # represent which boundary covers which pixels.
        im = cairo.ImageSurface(cairo.FORMAT_RGB24, size, size)

	# Color helpers.
    def get_rgba_component(c):
        return c if isinstance(c, float) else c/255.0
    def get_rgba_tuple(clr, alpha=.25):
        # Colors are specified as tuples/lists with 3 (RGB) or 4 (RGBA)
        # components. Components that are float values must be in the
        # range 0-1, while all other values are in the range 0-255.
        # Because .gif does not support partial transparency, alpha values
        # are forced to 1.
        return (get_rgba_component(clr[0]), get_rgba_component(clr[1]), get_rgba_component(clr[2]),
            get_rgba_component(clr[3]) if len(clr) == 4 and format != 'gif' else (alpha if format != 'gif' else 1.0))

    # Create the drawing surface.
    ctx = cairo.Context(im)
    ctx.select_font_face(maps_settings.MAP_LABEL_FONT, cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL)
    
    if format in ('json', 'jsonp'):
        # For the UTF-8 Grid response, turn off anti-aliasing since the color we draw to each pixel
        # is a code for what is there.
        ctx.set_antialias(cairo.ANTIALIAS_NONE)
    
    def max_extent(shape):
        a, b, c, d = shape.extent
        return max(c-a, d-b)
    
    # Transform the boundaries to output coordinates.
    draw_shapes = []
    for bdry in boundaries:
        if not "style" in bdry: continue # Boundary had no corresponding MapLayerBoundary
        
        shape = bdry[shape_field]
        
        # Simplify to the detail that could be visible in the output. Although
        # simplification may be a little expensive, drawing a more complex
        # polygon is even worse.
        try:
            shape = shape.simplify(pixel_width, preserve_topology=True)
        except: # GEOSException
            pass # try drawing original
        
        # Make sure the results are all MultiPolygons for consistency.
        if shape.__class__.__name__ == 'Polygon':
            shape = MultiPolygon((shape,), srid=db_srs.srid)
        else:
            # Be sure to override SRS (for Google, see above). This code may
            # never execute?
            shape = MultiPolygon(list(shape), srid=db_srs.srid)

        # Is this shape too small to be visible?
        ext_dim = max_extent(shape)
        if ext_dim < pixel_width:
            continue

        # Convert the shape to the output SRS.
        shape.transform(out_srs)
        
        draw_shapes.append( (len(draw_shapes), bdry, shape, ext_dim) )
        
    # Draw shading, for each linear ring of each polygon in the multipolygon.
    for i, bdry, shape, ext_dim in draw_shapes:
        if not bdry["style"].color and format not in ('json', 'jsonp'): continue
        for polygon in shape:
            for ring in polygon: # should just be one since no shape should have holes?
                color = bdry["style"].color
                
                if format in ('json', 'jsonp'):
                    # We're returning a "UTF-8 Grid" indicating which feature is at
                    # each pixel location on the grid. In order to compute the grid,
                    # we draw to an image surface with a distinct color for each feature.
                    # Then we convert the pixel data into the UTF-8 Grid format.
                    ctx.set_source_rgb(*[ (((i+1)/(256**exp)) % 256)/255.0 for exp in xrange(3) ])
                
                elif isinstance(color, (tuple, list)):
                    # Specify a 3/4-tuple (or list) for a solid color.
                    ctx.set_source_rgba(*get_rgba_tuple(color))
                        
                elif isinstance(color, dict):
                    # Specify a dict of the form { "color1": (R,G,B), "color2": (R,G,B) } to
                    # create a solid fill of color1 plus smaller stripes of color2.
                    if color.get("color", None) != None:
                        ctx.set_source_rgba(*get_rgba_tuple(color["color"]))
                    elif color.get("color1", None) != None and color.get("color2", None) != None:
                        pat = cairo.LinearGradient(0.0, 0.0, size, size)
                        for x in xrange(0,size, 32): # divisor of the size so gradient ends at the end
                            pat.add_color_stop_rgba(*([float(x)/size] + list(get_rgba_tuple(color["color1"], alpha=.3))))
                            pat.add_color_stop_rgba(*([float(x+28)/size] + list(get_rgba_tuple(color["color1"], alpha=.3))))
                            pat.add_color_stop_rgba(*([float(x+28)/size] + list(get_rgba_tuple(color["color2"], alpha=.4))))
                            pat.add_color_stop_rgba(*([float(x+32)/size] + list(get_rgba_tuple(color["color2"], alpha=.4))))
                        ctx.set_source(pat)
                    else:
                        continue # skip fill
                else:
                    continue # Unknown color data structure.
                ctx.new_path()
                for pt in ring.coords:
                    ctx.line_to(*viewport(pt))
                ctx.fill()
                
    # Draw outlines, for each linear ring of each polygon in the multipolygon.
    for i, bdry, shape, ext_dim in draw_shapes:
        if format in ('json', 'jsonp'): continue
        if ext_dim < pixel_width * 3: continue # skip outlines if too small
        color = bdry["style"].color
        for polygon in shape:
            for ring in polygon: # should just be one since no shape should have holes?
                ctx.new_path()
                for pt in ring.coords:
                    ctx.line_to(*viewport(pt))
                    
                if not isinstance(color, dict) or not "border" in color or not "width" in color["border"]:
                    if ext_dim < pixel_width * 60:
                        ctx.set_line_width(1)
                    else:
                        ctx.set_line_width(2.5)
                else:
                    ctx.set_line_width(color["border"]["width"])
                    
                if not isinstance(color, dict) or not "border" in color or not "color" in color["border"]:
                    ctx.set_source_rgba(.3,.3,.3, .75)  # grey, semi-transparent
                else:
                    ctx.set_source_rgba(*get_rgba_tuple(color["border"]["color"], alpha=.75))
                ctx.stroke_preserve()
                
    # Draw labels.
    for i, bdry, shape, ext_dim in draw_shapes:
        if format in ('json', 'jsonp'): continue
        if ext_dim < pixel_width * 20: continue
        
        color = bdry["style"].color
        if isinstance(color, dict) and "label" in color and color["label"] == None: continue
        
        # Get the location of the label stored in the database, or fall back to
        # GDAL routine point_on_surface to get a point quickly.
        if bdry["style"].label_point:
            # Override the SRS on the point (for Google, see above). Then transform
            # it to world coordinates.
            pt = Point(tuple(bdry["style"].label_point), srid=db_srs.srid)
            pt.transform(out_srs)
        elif bdry["label_point"]:
            # Same transformation as above.
            pt = Point(tuple(bdry["label_point"]), srid=db_srs.srid)
            pt.transform(out_srs)
        else:
            # No label_point is specified so try to find one by using the
            # point_on_surface to find a point that is in the shape and
            # in the viewport's bounding box.
            try:
                pt = bbox.intersection(shape).point_on_surface
            except:
                # Don't know why this would fail. Bad geometry of some sort.
                # But we really don't want to leave anything unlabeled so
                # try the center of the bounding box.
                pt = bbox.centroid
                if not shape.contains(pt):
                    continue
        
        # Transform to world coordinates and ensure it is within the bounding box.
        if not bbox.contains(pt):
            # If it's not in the bounding box and the shape occupies most of this
            # bounding box, try moving the point to somewhere in the current tile.
            try:
                inters = bbox.intersection(shape)
                if inters.area < bbox.area/3: continue
                pt = inters.point_on_surface
            except:
                continue
        pt = viewport(pt)
        
        txt = bdry["name"]
        if isinstance(bdry["style"].metadata, dict): txt = bdry["style"].metadata.get("label", txt)
        if ext_dim > size * pixel_width:
            ctx.set_font_size(18)
        else:
            ctx.set_font_size(12)
        x_off, y_off, tw, th = ctx.text_extents(txt)[:4]
        
        # Is it within the rough bounds of the shape and definitely the bounds of this tile?
        if tw < ext_dim/pixel_width/5 and th < ext_dim/pixel_width/5 \
            and pt[0]-x_off-tw/2-4 > 0 and pt[1]-th-4 > 0 and pt[0]-x_off+tw/2+7 < size and pt[1]+6 < size:
            # Draw the background rectangle behind the text.
            ctx.set_source_rgba(0,0,0,.55)  # black, some transparency
            ctx.new_path()
            ctx.line_to(pt[0]-x_off-tw/2-4,pt[1]-th-4)
            ctx.rel_line_to(tw+9, 0)
            ctx.rel_line_to(0, +th+8)
            ctx.rel_line_to(-tw-9, 0)
            ctx.fill()
            
            # Now a drop shadow (also is partially behind the first rectangle).
            ctx.set_source_rgba(0,0,0,.3)  # black, some transparency
            ctx.new_path()
            ctx.line_to(pt[0]-x_off-tw/2-4,pt[1]-th-4)
            ctx.rel_line_to(tw+11, 0)
            ctx.rel_line_to(0, +th+10)
            ctx.rel_line_to(-tw-11, 0)
            ctx.fill()
            
            # Draw the text.
            ctx.set_source_rgba(1,1,1,1)  # white
            ctx.move_to(pt[0]-x_off-tw/2,pt[1])
            ctx.show_text(txt)
                
    if format in ("png", "gif"):
        # Convert the image buffer to raw bytes.
        buf = StringIO()
        im.write_to_png(buf)
        v = buf.getvalue()
        if format == "gif": v = convert_png_to_gif(v)
        
        # Form the response.
        r = HttpResponse(v, content_type='image/' + format)
        r["Content-Length"] = len(v)
    
    elif format == "svg":
        im.finish()
        v = buf.getvalue()
        r = HttpResponse(v, content_type='image/svg+xml')
        r["Content-Length"] = len(v)
    
    elif format in ('json', 'jsonp'):
        # Get the bytes, which are RGBA sequences.
        buf1 = list(im.get_data())
        
        # Convert the 4-byte sequences back into integers that refer back to
        # the boundary list. Count the number of pixels for each shape.
        shapeidx = []
        shapecount = { }
        for i in xrange(0, size*size):
            b = ord(buf1[i*4+2])*(256**0) + ord(buf1[i*4+1])*(256**1) + ord(buf1[i*4+0])*(256**2)
            shapeidx.append(b)
            if b > 0: shapecount[b] = shapecount.get(b, 0) + 1
            
        # Assign low unicode code points to the most frequently occuring pixel values,
        # except always map zero to character 32.
        shapecode1 = { }
        shapecode2 = { }
        for k, count in sorted(shapecount.items(), key = lambda kv : kv[1]):
            b = len(shapecode1) + 32 + 1
            if b >= 34: b += 1
            if b >= 92: b += 1
            shapecode1[k] = b
            shapecode2[b] = draw_shapes[k-1]
            
        buf = ''
        if format == 'jsonp': buf += request.GET.get("callback", "callback") + "(\n"
        buf += '{"grid":['
        for row in xrange(size):
            if row > 0: buf += ",\n         "
            buf += json.dumps(u"".join(unichr(shapecode1[k] if k != 0 else 32) for k in shapeidx[row*size:(row+1)*size]))
        buf += "],\n"
        buf += ' "keys":' + json.dumps([""] + [shapecode2[k][1]["slug"] for k in sorted(shapecode2)], separators=(',', ':')) + ",\n"
        buf += ' "data":' + json.dumps(dict( 
                    (shapecode2[k][1]["slug"], {
                            "name": shapecode2[k][1]["name"],
                    })
                    for k in sorted(shapecode2)), separators=(',', ':'))
        buf += "}"
        if format == 'jsonp': buf += ")"
        
        if format == "json":
            r = HttpResponse(buf, content_type='application/json')
        else:
            r = HttpResponse(buf, content_type='text/javascript')
    
    return r
def get_or_create_pngserie_with_defaultlegend_from_old_results(scenario, pt):
    log.debug('!!!get_or_create_pngserie_with_defaultlegend_from_old_results')

    result = Result.objects.filter(
        scenario=scenario,
        resulttype__resulttype_presentationtype__presentationtype=pt)

    if result.count() > 0:
        #check if layer is already there
        result = result[0]
        log.info('result found')

        pl, pl_new = PresentationLayer.objects.get_or_create(
            presentationtype=pt,
            scenario_presentationlayer__scenario=scenario,
            defaults={"value": result.value})
        if pl_new:
            Scenario_PresentationLayer.objects.create(
                scenario=scenario, presentationlayer=pl)
            log.info("pl_new id: " + str(pl.id))

        pl.value = result.value
        pl.save()

        try:
            log.info(result.resultpngloc)
            dest_dir = Setting.objects.get(key='DESTINATION_DIR').value
            presentation_dir = Setting.objects.get(
                key='PRESENTATION_DIR').value
            if result.resultpngloc is not None:
                log.debug('read grid information from pgw en png file!')
                resultpngloc = result.resultpngloc.replace('\\', '/')
                png_name = os.path.join(dest_dir, resultpngloc)

                if result.resulttype.overlaytype == 'ANIMATEDMAPOVERLAY':
                    numberstring = '%4i' % result.firstnr
                    numberstring = numberstring.replace(" ", "0")
                    png_name = png_name.replace('####', numberstring)

                pgw_name = png_name.replace(".png", ".pgw")
                pgwfile = open(pgw_name, 'r')
                pgwfields = pgwfile.readlines()
                pgwfile.close()
                gridsize, a, b, c, west, north = [float(s) for s in pgwfields]

                picture = Image.open(png_name)
                width, height = picture.size
                east = west + width * gridsize
                south = north - height * gridsize

                old_file = resultpngloc.split('/')
                output_dir_name = os.path.join(
                    'flooding', 'scenario', str(scenario.id), old_file[-2])
                output_file_name = os.path.join(output_dir_name, old_file[-1])

                s_dir = os.path.join(dest_dir, os.path.dirname(resultpngloc))

                #destination dir
                d_dir = os.path.join(presentation_dir, output_dir_name)
                if os.path.isdir(d_dir):
                    rmtree(d_dir)  # or move(presentation_dir +
                                   # output_dir_name, presentation_dir
                                   # + output_dir_name + '_old')

                log.debug('source dir is ' + str(s_dir))
                log.debug('destination dir is ' + str(d_dir))
                copytree(s_dir, d_dir)

                if result.resulttype.overlaytype == 'ANIMATEDMAPOVERLAY':
                    stype = PresentationSource.SOURCE_TYPE_SERIE_PNG_FILES
                else:
                    stype = PresentationSource.SOURCE_TYPE_PNG_FILE

                source, new = PresentationSource.objects.get_or_create(
                    file_location=output_file_name, type=stype)
                source.t_source = datetime.datetime.now()
                source.save()

                grid, new = PresentationGrid.objects.get_or_create(
                    presentationlayer=pl,
                    defaults={
                        'rownr': height, 'colnr': width, 'gridsize': gridsize})
                grid.bbox_orignal_srid = 28992
                grid.png_default_legend = source

                #left lower
                ll = Point(south, west, srid=grid.bbox_orignal_srid)
                #right upper
                ru = Point(north, east, srid=grid.bbox_orignal_srid)
                poly = Polygon([ll, ru, ru, ll])
                poly.transform(4326)
                grid.extent = MultiPolygon(poly)
                grid.save()

                if result.resulttype.overlaytype == 'ANIMATEDMAPOVERLAY':
                    if not result.startnr:
                        result.startnr = result.firstnr

                    animation, new = Animation.objects.get_or_create(
                        presentationlayer=pl, defaults={
                            'firstnr': result.firstnr,
                            'lastnr': result.lastnr,
                            'startnr': result.startnr,
                            'delta_timestep': (1 / 24)})

                    animation.firstnr = result.firstnr
                    animation.lastnr = result.lastnr
                    animation.startnr = result.startnr
                    log.debug(
                        "save animation with numbers %i tot %i" %
                        (result.firstnr, result.lastnr))
                    animation.save()

        except IOError as e:
            log.error('error creating source')
            log.error(','.join(map(str, e.args)))
            if pl_new:
                pl.delete()
Example #20
0
def get_or_create_pngserie_with_defaultlegend_from_old_results(scenario, pt):
    log.debug('!!!get_or_create_pngserie_with_defaultlegend_from_old_results')
    result = Result.objects.filter(
        scenario=scenario,
        resulttype__resulttype_presentationtype__presentationtype=pt)

    if result.count() > 0:
        #check if layer is already there
        result = result[0]
        log.info('result found')

        pl, pl_new = PresentationLayer.objects.get_or_create(
            presentationtype=pt,
            scenario_presentationlayer__scenario=scenario,
            defaults={"value": result.value})
        if pl_new:
            Scenario_PresentationLayer.objects.create(scenario=scenario,
                                                      presentationlayer=pl)
            log.info("pl_new id: " + str(pl.id))

        pl.value = result.value
        pl.save()

        try:
            log.info(result.resultpngloc)
            dest_dir = Setting.objects.get(key='DESTINATION_DIR').value
            # Make sure every directory separator is consistent
            #dest_dir = dest_dir.replace('\\', os.sep).replace('/', os.sep)
            presentation_dir = Setting.objects.get(
                key='PRESENTATION_DIR').value
            #presentation_dir = presentation_dir.replace('\\', os.sep).replace('/', os.sep)
            if result.resultpngloc is not None:
                log.debug('read grid information from pgw en png file!')
                resultpngloc = result.resultpngloc.replace('\\', '/')
                png_name = os.path.join(dest_dir, resultpngloc)

                if result.resulttype.overlaytype == 'ANIMATEDMAPOVERLAY':
                    numberstring = '%4i' % result.firstnr
                    numberstring = numberstring.replace(" ", "0")
                    png_name = png_name.replace('####', numberstring)

                pgw_name = png_name.replace(".png", ".pgw")

                pgwfile = open(pgw_name, 'r')
                pgwfields = pgwfile.readlines()
                pgwfile.close()
                gridsize, a, b, c, west, north = [float(s) for s in pgwfields]

                picture = Image.open(png_name)
                width, height = picture.size
                east = west + width * gridsize
                south = north - height * gridsize

                old_file = resultpngloc.split('/')
                output_dir_name = os.path.join('flooding', 'scenario',
                                               str(scenario.id), old_file[-2])
                output_file_name = os.path.join(output_dir_name, old_file[-1])

                s_dir = os.path.join(dest_dir,
                                     os.path.dirname(resultpngloc)).replace(
                                         '\\', os.sep)

                #destination dir
                d_dir = os.path.join(presentation_dir,
                                     output_dir_name).replace('\\', os.sep)
                if os.path.isdir(d_dir):
                    rmtree(d_dir)  # or move(presentation_dir +
                    # output_dir_name, presentation_dir
                    # + output_dir_name + '_old')

                log.debug('source dir is {0}'.format(s_dir.encode('utf-8')))
                log.debug('destination dir is {0}'.format(
                    d_dir.encode('utf-8')))
                copytree(s_dir, d_dir)

                if result.resulttype.overlaytype == 'ANIMATEDMAPOVERLAY':
                    stype = PresentationSource.SOURCE_TYPE_SERIE_PNG_FILES
                else:
                    stype = PresentationSource.SOURCE_TYPE_PNG_FILE

                source, new = PresentationSource.objects.get_or_create(
                    file_location=output_file_name, type=stype)
                source.t_source = datetime.datetime.now()
                source.save()

                grid, new = PresentationGrid.objects.get_or_create(
                    presentationlayer=pl,
                    defaults={
                        'rownr': height,
                        'colnr': width,
                        'gridsize': gridsize
                    })
                grid.bbox_orignal_srid = 28992
                grid.png_default_legend = source

                #left lower
                ll = Point(south, west, srid=grid.bbox_orignal_srid)
                #right upper
                ru = Point(north, east, srid=grid.bbox_orignal_srid)
                poly = Polygon([ll, ru, ru, ll])
                poly.transform(4326)
                grid.extent = MultiPolygon(poly)
                grid.save()

                if result.resulttype.overlaytype == 'ANIMATEDMAPOVERLAY':
                    if not result.startnr:
                        result.startnr = result.firstnr

                    animation, new = Animation.objects.get_or_create(
                        presentationlayer=pl,
                        defaults={
                            'firstnr': result.firstnr,
                            'lastnr': result.lastnr,
                            'startnr': result.startnr,
                            'delta_timestep': (1 / 24)
                        })

                    animation.firstnr = result.firstnr
                    animation.lastnr = result.lastnr
                    animation.startnr = result.startnr
                    log.debug("save animation with numbers %i tot %i" %
                              (result.firstnr, result.lastnr))
                    animation.save()

        except IOError as e:
            log.error('error creating source')
            log.error(','.join(map(str, e.args)))
            if pl_new:
                pl.delete()
Example #21
0
    def output_kml(point_index_and_triangle_indices):
        point_index, triangle_indices = point_index_and_triangle_indices

        centre_x = x[point_index]
        centre_y = y[point_index]
        position_tuple = centre_x, centre_y

        if len(triangle_indices) < 3:
            # Skip any point with fewer than 3 triangle_indices
            return

        if position_tuple in position_to_postcodes:
            postcodes = sorted(position_to_postcodes[position_tuple])
            file_basename = postcodes[0]
            outcode = file_basename.split()[0]
        else:
            postcodes = []
            file_basename = 'point-{0:09d}'.format(point_index)
            outcode = 'points-at-infinity'

        mkdir_p(join(postcodes_output_directory, outcode))

        leafname = file_basename + ".kml"

        if len(postcodes) > 1:
            json_leafname = file_basename + ".json"
            with open(join(postcodes_output_directory, outcode, json_leafname), "w") as fp:
                json.dump(postcodes, fp)

        kml_filename = join(postcodes_output_directory, outcode, leafname)

        if not os.path.exists(kml_filename):

            circumcentres = [ccs[i] for i in triangle_indices]

            def compare_points(a, b):
                ax = a[0] - centre_x
                ay = a[1] - centre_y
                bx = b[0] - centre_x
                by = b[1] - centre_y
                angle_a = math.atan2(ay, ax)
                angle_b = math.atan2(by, bx)
                result = angle_b - angle_a
                if result > 0:
                    return 1
                elif result < 0:
                    return -1
                return 0

            sccs = np.array(sorted(circumcentres, cmp=compare_points))
            xs = [cc[0] for cc in sccs]
            ys = [cc[1] for cc in sccs]

            border = []
            for i in range(0, len(sccs) + 1):
                index_to_use = i
                if i == len(sccs):
                    index_to_use = 0
                cc = (float(xs[index_to_use]),
                      float(ys[index_to_use]))
                border.append(cc)

            polygon = Polygon(border, srid=27700)
            wgs_84_polygon = polygon.transform(4326, clone=True)

            # If the polygon isn't valid after transformation, try to
            # fix it. (There is one such case.)
            if not wgs_84_polygon.valid:
                tqdm.write("Warning: had to fix polygon {0}".format(kml_filename))
                wgs_84_polygon = fix_invalid_geos_geometry(wgs_84_polygon)

            requires_clipping = polygon_requires_clipping(wgs_84_polygon)
            if requires_clipping:
                try:
                    if wgs_84_polygon.intersects(uk_multipolygon):
                        clipped_polygon = wgs_84_polygon.intersection(uk_multipolygon)
                    else:
                        clipped_polygon = wgs_84_polygon
                except Exception, e:
                    tqdm.write("Got exception when generating:", kml_filename)
                    tqdm.write("The exception was:", e)
                    tqdm.write("The polygon's KML was:", wgs_84_polygon.kml)
                    clipped_polygon = wgs_84_polygon
            else:
                clipped_polygon = wgs_84_polygon

            output_boundary_kml(kml_filename, requires_clipping, postcodes, clipped_polygon)
Example #22
0
    def kml_chunk(self, n, s, e, w):
        """
        Get the kml of a lat/lon bounded part of the study region, 
        with geometry simplified in proportion to the visible % of the region
        """

        bounds = Polygon(
            LinearRing([
                Point(w, n),
                Point(e, n),
                Point(e, s),
                Point(w, s),
                Point(w, n)
            ]))
        bounds.set_srid(4326)
        center_lat = bounds.centroid.y  # in 4326 because it is used only for setting up the subregion calls
        center_lon = bounds.centroid.x  # in 4326 because it is used only for setting up the subregion calls
        bounds.transform(settings.GEOMETRY_DB_SRID)

        # all longitudinal width calcs should be done in GEOMETRY_DB_SRID - 4326 can fail across the date line
        zoom_width = (Point(bounds.extent[0], bounds.centroid.y)).distance(
            Point(bounds.extent[2], bounds.centroid.y))

        full_shape_width = (Point(self.geometry.extent[0],
                                  self.geometry.centroid.y)).distance(
                                      Point(self.geometry.extent[2],
                                            self.geometry.centroid.y))

        # The following simplify values can be tuned to your preference
        # minimum geometry simplify value (highest detail) = 50 (arbitrary, based on observation)
        # maximum geometry simplify value = 200 (arbitrary, based on observation)
        # value set by pecentage of study region width requested in this chunk
        min_simplify_val = 50.0
        max_simplify_val = 200.0
        simplify_factor = max(
            min_simplify_val,
            min(max_simplify_val,
                max_simplify_val * zoom_width / full_shape_width))

        transform_geom = self.geometry.simplify(simplify_factor,
                                                preserve_topology=True)
        transform_geom = transform_geom.intersection(bounds)
        transform_geom.transform(4326)

        # Debugging info
        #print zoom_width
        #print full_shape_width
        #print simplify_factor
        #print transform_geom.num_coords
        # End debugging info

        # only add sub-regions if this is not our highest detail level
        bLastLodLevel = simplify_factor < max_simplify_val  # change this last value to build varying levels of LOD
        max_lod_pixels = 500
        min_lod_pixels = 250

        # make sure the most detailed lod stays active no matter how close user zooms
        if bLastLodLevel:
            max_lod_pixels = -1

        retval = '<Region><LatLonAltBox><north>%f</north><south>%f</south><east>%f</east><west>%f</west></LatLonAltBox><Lod><minLodPixels>%f</minLodPixels><maxLodPixels>%f</maxLodPixels><minFadeExtent>0</minFadeExtent><maxFadeExtent>0</maxFadeExtent></Lod></Region>' % (
            n, s, e, w, min_lod_pixels, max_lod_pixels
        ) + '<Placemark> <name>Study Region Boundaries</name><Style> <LineStyle> <color>ff00ffff</color> <width>2</width> </LineStyle> <PolyStyle> <color>8000ffff</color> </PolyStyle></Style>%s</Placemark>' % (
            transform_geom.kml, )

        # conditionally add sub-regions
        if not bLastLodLevel:
            subregions = '<Folder><name>Study Region LODs</name>' + '<Folder><name>SE</name>' + self.kml_chunk(
                center_lat, s, e, center_lon) + '</Folder>'

            subregions = subregions + '<Folder><name>NE</name>' + self.kml_chunk(
                n, center_lat, e, center_lon) + '</Folder>'

            subregions = subregions + '<Folder><name>SW</name>' + self.kml_chunk(
                center_lat, s, center_lon, w) + '</Folder>'

            subregions = subregions + '<Folder><name>NW</name>' + self.kml_chunk(
                n, center_lat, center_lon, w) + '</Folder>'

            retval = retval + subregions + '</Folder>'

        return retval