polygon.py

#
from geometry import *
from bbox import BBox
import utils
#

DEPRECATED! we use shapely.geometry.MultiPolygon instead

#

Complex polygons

class MultiPolygon(SolidGeometry):
#
#
    def __init__(self, contours):
        self.__area = None
        self.__areas = None
        self.__centroid = None
        self.apply_contours(contours)
#

constructs a Polygon from contours

    @staticmethod
    def fromPoly(poly):
        contours = []
        for i in range(len(poly)):
            pts = poly.contour(i)
            contours.append(pts)
        return MultiPolygon(contours)

    def apply_contours(self, contours):
#
        self.contours = contours
        from Polygon import Polygon as GPCPoly
        poly = GPCPoly()
        skip = 0
        for pts_ in contours:
            pts = []
            for pt in pts_:
                if 'deleted' in pt and pt.deleted is True:
                    skip += 1
                    continue
                pts.append((pt[0], pt[1]))
            ishole = utils.is_clockwise(pts)

            if len(pts) > 2:
                poly.addContour(pts, ishole)
        self.poly = poly
#
    def area(self):
        if self.__area is not None:
            return self.__area
        self.__area = self.poly.area()
        return self.__area
#

returns array of areas of all sub-polygons areas of holes are < 0

    def areas(self):
#
        if self.__areas is not None:
            return self.__areas
        a = []
        for i in range(len(self.poly)):
            t = self.poly.area(i)
            if self.poly.isHole(i):
                t *= -1
            a.append(t)
        self.__areas = a
        return a
#

returns the center of gravity for this polygon

    def centroid(self):
#
        if self.__centroid is not None:
            return self.__centroid
        self.__centroid = self.poly.center()
        return self.__centroid
#

smart bounding box

    def bbox(self, min_area=0):
#
        bb = []
        bbox = BBox()
        if min_area == 0:
            bb.append(self.poly.boundingBox())
        else:
            areas = self.areas()
            max_a = max(areas)
            for i in range(len(self.poly)):
                if self.poly.isHole(i):
                    continue
                a = areas[i]
                if a < max_a * min_area:
                    continue
                bb.append(self.poly.boundingBox(i))
        for b in bb:
            bbox.update((b[0], b[2]))
            bbox.update((b[1], b[2]))
            bbox.update((b[0], b[3]))
            bbox.update((b[1], b[3]))
        return bbox
#

returns a new multi-polygon whose contours are projected to a map projection

    def project(self, proj):
#
        in_contours = self.contours
        out_contours = []
        for pts in in_contours:
            pcont = proj.plot(pts)
            if pcont != None:
                out_contours += pcont
        return MultiPolygon(out_contours)
#

returns a new multi-polygon whose contours are projected to a new view

    def project_view(self, view):
#
        contours = self.contours
        out = []
        for contour in contours:
            out_c = []
            for pt in contour:
                pt = view.project(pt)
                out_c.append(pt)
            out.append(out_c)
        self.contours = out
        out_poly = MultiPolygon(out)
        return out_poly
#
    def crop_to(self, view_bounds):
        poly = self.poly & view_bounds.poly
        return MultiPolygon.fromPoly(poly)
#
    def substract_geom(self, geom):
        if not isinstance(geom, MultiPolygon):
            raise NotImplementedError('substraction is allowed for polygons only, yet')
        poly = self.poly - geom.poly
        return MultiPolygon.fromPoly(poly)
#

constructs a svg representation of this polygon

    def to_svg(self, svg, round):
#
        path_str = ""
        if round is False:
            fmt = '%f,%f'
        else:
            fmt = '%.' + str(round) + 'f'
            fmt = fmt + ',' + fmt

        for pts in self.contours:
            cont_str = ""
            kept = []
            for pt in pts:
                if 'deleted' in pt and pt.deleted is True:
                    continue
                kept.append(pt)

            if len(kept) <= 3:
                continue
            for pt in kept:
                if cont_str == "":
                    cont_str = "M"
                else:
                    cont_str += "L"
                cont_str += fmt % pt
            cont_str += "Z "
            path_str += cont_str

        if path_str == "":
            return None

        path = svg.node('path', d=path_str)
        return path
#
    def is_empty(self):
        return len(self.contours) == 0
#
    def unify(self, point_store, precision=None):
        from kartograph.simplify import unify_polygons
        contours = self.contours
        contours = unify_polygons(contours, point_store, precision)
        self.apply_contours(contours)
#
    def points(self):
        return self.contours
#

is called after the points of this geometry are changed from outside this class

    def update(self):
#
        self.apply_contours(self.contours)
#
    def to_kml(self, round=None):
        from pykml.factory import KML_ElementMaker as KML
        poly = KML.Polygon(
            KML.tesselate("1")
        )
        outer = KML.outerBoundaryIs()
        inner = KML.innerBoundaryIs()
        has_inner = False
        for i in range(len(self.poly)):
            cnt = self.poly[i]
            coords = ''
            for p in cnt:
                coords += ','.join(map(str, p)) + ' '
            ring = KML.LinearRing(
                KML.coordinates(coords)
            )
#

hole = self.poly.isHole(i) if hole == False:

            outer.append(ring)
#

else: inner.append(ring) has_inner = True

        poly.append(outer)
        if has_inner:
            poly.append(inner)

        return poly
#

splits the geometry into several line segments

    def to_line_segments(self):
#
        self.lines = []
#
    def from_line_segments(self):
        self.lines = []
#
class Polygon(SolidGeometry):
#
    def __init__(self, points):
        self.__area = None
        self.__centroid = None
        self.points = points
#
    def area(self):
        if self.__area is not None:
            return self.__area
        a = 0
        pts = self.points
        for i in range(len(pts) - 1):
            p0 = pts[i]
            p1 = pts[i + 1]
            a += p0.x * p1.y - p1.x * p0.y
        self.__area = abs(a) * .5
        return self.__area
#
    def project(self, proj):
        self.invalidate()
        self.points = proj.plot(self.points)