def generate_offset_track(self, mid_track, thickness): poly_line = LinearRing(mid_track) poly_line_offset_left = poly_line.parallel_offset(thickness, side="left", resolution=16, join_style=2, mitre_limit=2) poly_line_offset_right = poly_line.parallel_offset(thickness, side="right", resolution=16, join_style=2, mitre_limit=2) offset_left_track = [] for coord in poly_line_offset_left.coords: p = [coord[0], coord[1]] offset_left_track.append(p) offset_left_track.append(offset_left_track[0]) offset_right_track = [] for coord in poly_line_offset_right.coords: p = [coord[0], coord[1]] offset_right_track.append(p) offset_right_track.append(offset_right_track[0]) return offset_left_track, offset_right_track
def test_stitch(self): # The following LinearRing wanders in/out of the map domain # but importantly the "vertical" lines at 0'E and 360'E are both # chopped by the map boundary. This results in their ends being # *very* close to each other and confusion over which occurs # first when navigating around the boundary. # Check that these ends are stitched together to avoid the # boundary ordering ambiguity. # NB. This kind of polygon often occurs with MPL's contouring. coords = [(0.0, -70.70499926182919), (0.0, -71.25), (0.0, -72.5), (0.0, -73.49076371657017), (360.0, -73.49076371657017), (360.0, -72.5), (360.0, -71.25), (360.0, -70.70499926182919), (350, -73), (10, -73)] src_proj = ccrs.PlateCarree() target_proj = ccrs.Stereographic(80) linear_ring = LinearRing(coords) rings, mlinestr = target_proj.project_geometry(linear_ring, src_proj) self.assertEqual(len(mlinestr), 1) self.assertEqual(len(rings), 0) # Check the stitch works in either direction. linear_ring = LinearRing(coords[::-1]) rings, mlinestr = target_proj.project_geometry(linear_ring, src_proj) self.assertEqual(len(mlinestr), 1) self.assertEqual(len(rings), 0)
def boundary_of_drivemap(drivemap, footprint, height=1.0, edge_width=0.25): """ Construct an object boundary using the manually recorded corner points. Do this by finding all the points in the drivemap along the footprint. Use the bottom 'height' meters of the drivemap (not trees). Resulting pointcloud has the same SRS and offset as the input. Arguments: drivemap : pcl.PointCloud footprint : pcl.PointCloud height : Cut-off height, points more than this value above the lowest point of the drivemap are considered trees, and dropped. default 1 m. edge_width : Points belong to the boundary when they are within this distance from the footprint. default 0.25 Returns: boundary : pcl.PointCloud """ # construct basemap as the bottom 'height' meters of the drivemap drivemap_array = np.asarray(drivemap) bb = BoundingBox(points=drivemap_array) basemap = extract_mask(drivemap, drivemap_array[:, 2] < bb.min[2] + height) # Cut band between +- edge_width around the footprint edge = LinearRing(np.asarray(footprint)).buffer(edge_width) boundary = extract_mask(basemap, [edge.contains(asPoint(pnt)) for pnt in basemap]) utils.force_srs(boundary, same_as=basemap) return boundary
def intersections(a, b): ea = LinearRing(a) eb = LinearRing(b) mp = ea.intersection(eb) x = [p.x for p in mp] y = [p.y for p in mp] return x, y
def test_linearring_from_coordinate_sequence(): expected_coords = [(0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (0.0, 0.0)] ring = LinearRing(((0.0, 0.0), (0.0, 1.0), (1.0, 1.0))) assert ring.coords[:] == expected_coords ring = LinearRing([(0.0, 0.0), (0.0, 1.0), (1.0, 1.0)]) assert ring.coords[:] == expected_coords
def test_linearring_from_empty(): ring = LinearRing() assert ring.is_empty assert ring.coords[:] == [] ring = LinearRing([]) assert ring.is_empty assert ring.coords[:] == []
def test_linearring_from_short_closed_linestring(self): # Create linearring from linestring where the coordinate sequence is # too short but appears to be closed (first and last coordinates # are the same) coords = [(0.0, 0.0), (0.0, 0.0), (0.0, 0.0)] line = LineString(coords) ring1 = LinearRing(coords) ring2 = LinearRing(line) assert ring1.coords[:] == ring2.coords[:]
def test_linearring_from_empty(): ring = LinearRing() assert ring.is_empty assert isinstance(ring.coords, CoordinateSequence) assert ring.coords[:] == [] ring = LinearRing([]) assert ring.is_empty assert isinstance(ring.coords, CoordinateSequence) assert ring.coords[:] == []
def ellipse_intersect(a, b, ret_points=False): ea = LinearRing(a) eb = LinearRing(b) mp = ea.intersection(eb) if ret_points: x = [p.x for p in mp] y = [p.y for p in mp] return x, y return bool(mp)
def test_linearring_mutate(self): coords = ((0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (1.0, 0.0)) ring = LinearRing(coords) # Coordinate modification ring.coords = ((0.0, 0.0), (0.0, 2.0), (2.0, 2.0), (2.0, 0.0)) self.assertEqual( ring.__geo_interface__, {'type': 'LinearRing', 'coordinates': ((0.0, 0.0), (0.0, 2.0), (2.0, 2.0), (2.0, 0.0), (0.0, 0.0))})
def test_linearring(self): # Initialization # Linear rings won't usually be created by users, but by polygons coords = ((0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (1.0, 0.0)) ring = LinearRing(coords) self.assertEqual(len(ring.coords), 5) self.assertEqual(ring.coords[0], ring.coords[4]) self.assertEqual(ring.coords[0], ring.coords[-1]) self.assertTrue(ring.is_ring) # Ring from sequence of Points self.assertEqual(LinearRing((map(Point, coords))), ring)
def test_linearring_from_too_short_linestring(): # Creation of LinearRing request at least 3 coordinates (unclosed) or # 4 coordinates (closed) coords = [(0.0, 0.0), (1.0, 1.0)] line = LineString(coords) with pytest.raises(ValueError, match="at least 3 coordinate tuple"): LinearRing(line)
def test_cuts(self): # Check that fragments do not start or end with one of the # original ... ? linear_ring = LinearRing([(-10, 30), (10, 60), (10, 50)]) projection = ccrs.Robinson(170.5) rings, multi_line_string = projection.project_geometry(linear_ring) # The original ring should have been split into multiple pieces. self.assertGreater(len(multi_line_string), 1) self.assertFalse(rings) def assert_intersection_with_boundary(segment_coords): # Double the length of the segment. start = segment_coords[0] end = segment_coords[1] end = [end[i] + 2 * (end[i] - start[i]) for i in (0, 1)] extended_segment = sgeom.LineString([start, end]) # And see if it crosses the boundary. intersection = extended_segment.intersection(projection.boundary) self.assertFalse(intersection.is_empty, 'Bad topology near boundary') # Each line resulting from the split should start and end with a # segment that crosses the boundary when extended to double length. # (This is important when considering polygon rings which need to be # attached to the boundary.) for line_string in multi_line_string: coords = list(line_string.coords) self.assertGreaterEqual(len(coords), 2) assert_intersection_with_boundary(coords[1::-1]) assert_intersection_with_boundary(coords[-2:])
def test_polygon_from_linearring(): coords = [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)] ring = LinearRing(coords) polygon = Polygon(ring) assert polygon.exterior.coords[:] == coords assert len(polygon.interiors) == 0
def TMApoly(Fwaypt = None, fname_STA = None): MeterPntList = [] if Fwaypt != None: with open(Fwaypt,'r') as csvfile: line = csv.reader(csvfile) for field in line: MeterPntList.append([float(field[1]),float(field[0])]) # else: # All_Meter_Name = np.array([]) # for name in fname_STA: # a = np.genfromtxt(os.getcwd()+'\STA_DATA\\'+name, usecols = (6,7,8),dtype=[('RWY','S10'),('FAF','S10'),('MFX','S10')],delimiter=",") # All_Meter_Name = np.append(All_Meter_Name,a['MFX']) # All_Meter_Name = np.unique(All_Meter_Name) # with open('WayPoint1.csv','wb') as wcsvfile: # wri = csv.writer(wcsvfile) # for MFX in All_Meter_Name: # Coords = FixCoords(MFX) # if len(Coords) != 0: # MeterPntList.append(Coords) # wri.writerow(Coords+[MFX]) # else: # pass mlat = sum(x[0] for x in MeterPntList) / len(MeterPntList) mlng = sum(x[1] for x in MeterPntList) / len(MeterPntList) def algo(x): return (math.atan2(x[0] - mlat, x[1] - mlng) + 2 * math.pi) % (2*math.pi) MeterPntList.sort(key=algo) TMA_Ring = LinearRing(MeterPntList) TMA_Poly = Polygon(MeterPntList) return TMA_Poly, TMA_Ring, MeterPntList
def get_intersecting_boundaries_for_polygon(location: geodetic_polygon, boundary_table, engine, return_intersection_area=False, proximity_search = False, proximity_buffer = 0 ): """ Executes an intersection query for a polygon. :param location: location object :type location: :py:class:Geodetic2D :param boundary_table: The name of the service boundary table. :type boundary_table: `str` :param engine: SQLAlchemy database engine. :type engine: :py:class:`sqlalchemy.engine.Engine` :param return_intersection_area: Flag which triggers an area calculation on the Intersecting polygons. :type return_intersection_area: `bool` :return: A list of dictionaries containing the contents of returned rows. """ # Pull out just the number from the SRID trimmed_srid = int(location.spatial_ref.split('::')[1]) points = location.vertices ring = LinearRing(points) shapely_polygon = Polygon(ring) # load up a new Shapely Polygon from the WKT and convert it to a GeoAlchemy2 WKBElement # that we can use to query. poly = loads(shapely_polygon.wkt) wkb_poly = location.to_wkbelement(project_to=trimmed_srid) if proximity_search == True: return get_intersecting_boundaries_with_buffer(points[0][0], points[0][1], engine, boundary_table, wkb_poly, proximity_buffer, return_intersection_area) else: return _get_intersecting_boundaries_for_geom(engine, boundary_table, wkb_poly, return_intersection_area)
def to_poly(self): """Convert to a polygon. Returns: (shapely.geometry.polygon.LinearRing): Outline of the hexagon. """ centre = self.to_geographic() # the corners of a hexagon are at 60 degree increments. Start halfway # through an increment because we have flat topped ones DEG_TO_RAD = math.pi / 180 angles = [ 30 * DEG_TO_RAD, 90 * DEG_TO_RAD, 150 * DEG_TO_RAD, 210 * DEG_TO_RAD, 270 * DEG_TO_RAD, 330 * DEG_TO_RAD ] return LinearRing( [ ( centre[0] + self.D * math.sin(t), centre[1] + self.D * math.cos(t) ) for t in angles ] )
def is_ccw(self, region): coords = self.get_region_coords(region) ring = LinearRing(coords) if ring.is_ccw: return True else: return False
def add_box(ax, x0, x1, y0, y1, **kwargs): """ Add a polygon/box to any cartopy projection. Parameters ---------- ax : axes instance (should be from make_cartopy command) x0: float; western longitude bound of box. x1: float; eastern longitude bound of box. y0: float; southern latitude bound of box. y1: float; northern latitude bound of box. **kwargs: optional keywords Will modify the color, etc. of the bounding box. Returns ------- None Examples -------- import esm_analysis as et fig, ax = et.vis.make_cartopy() et.visualization.add_box(ax, [-150, -110, 30, 50], edgecolor='k', facecolor='#D3D3D3', linewidth=2, alpha=0.5) """ lons = [x0, x0, x1, x1] lats = [y0, y1, y1, y0] ring = LinearRing(list(zip(lons, lats))) ax.add_geometries([ring], ccrs.PlateCarree(), **kwargs)
def test_linearring_from_numpy(): # Construct from a numpy array np = pytest.importorskip("numpy") coords = [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)] ring = LinearRing(np.array(coords)) assert ring.coords[:] == [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)]
def highres_Orthographic(projection, r=R_Mars_km * 1e3): """ Increases the resolution of the circular outline in cartopy.crs.Orthographic projection. Parameters ---------- projection : obj A cartopy.crs.Orthographic() projection. r : float The radius of the globe in meters (e.g., for Mars this is the radius of Mars in meters). Returns ------- None. Changes the resolution of an existing projection. """ # re-implement the cartopy code to figure out the new boundary shape a = np.float(projection.globe.semimajor_axis or r) b = np.float(projection.globe.semiminor_axis or a) t = np.linspace(0, 2 * np.pi, 3601) coords = np.vstack([a * 0.99999 * np.cos(t), b * 0.99999 * np.sin(t)])[:, ::-1] # update the projection boundary projection._boundary = LinearRing(coords.T)
def get_intersecting_list_service_for_polygon(location: geodetic_polygon, boundary_table, engine, return_intersection_area=False, proximity_search = False, proximity_buffer = 0 ): """ Executes an intersection query for a polygon. :param location: location object :type location: :py:class:Geodetic2D :param boundary_table: The name of the service boundary table. :type boundary_table: `str` :param engine: SQLAlchemy database engine. :type engine: :py:class:`sqlalchemy.engine.Engine` :param return_intersection_area: Flag which triggers an area calculation on the Intersecting polygons. :type return_intersection_area: `bool` :return: A list of dictionaries containing the contents of returned rows. """ # Pull out just the number from the SRID trimmed_srid = int(location.spatial_ref.split('::')[1]) p = [] points = location.vertices for point in points: long, lat = gc_geom.reproject_point(point[0], point[1], trimmed_srid, 4326) p.append([long, lat]) ring = LinearRing(p) wkb_ring = location.to_wkbelement(project_to=trimmed_srid) return (_get_intersecting_list_service_for_geom(engine, i, wkb_ring, return_intersection_area) for i in boundary_table)
def test_out_of_bounds(self): # Check that a ring that is completely out of the map boundary # produces an empty result. # XXX Check efficiency? projection = ccrs.TransverseMercator(central_longitude=0) rings = [ # All valid ([(86, 1), (86, -1), (88, -1), (88, 1)], -1), # One NaN ([(86, 1), (86, -1), (130, -1), (88, 1)], 1), # A NaN segment ([(86, 1), (86, -1), (130, -1), (130, 1)], 1), # All NaN ([(120, 1), (120, -1), (130, -1), (130, 1)], 0), ] # Try all four combinations of valid/NaN vs valid/NaN. for coords, expected_n_lines in rings: linear_ring = LinearRing(coords) rings, mlinestr = projection.project_geometry(linear_ring) if expected_n_lines == -1: self.assertTrue(rings) self.assertFalse(mlinestr) else: self.assertEqual(len(mlinestr), expected_n_lines) if expected_n_lines == 0: self.assertTrue(mlinestr.is_empty)
def highres_NearsidePerspective(projection, altitude, r=R_Mars_km * 1e3): """ Increases the resolution of the circular outline in cartopy.crs.NearsidePerspective projection. Parameters ---------- projection : obj A cartopy.crs.NearsidePerspective() projection. altitude : int, float Apoapse altitude in meters. r : float The radius of the globe in meters (e.g., for Mars this is the radius of Mars in meters). Returns ------- None. Changes the resolution of an existing projection. """ # re-implement the cartopy code to figure out the new boundary shape a = np.float(projection.globe.semimajor_axis or r) h = np.float(altitude) max_x = a * np.sqrt(h / (2 * a + h)) t = np.linspace(0, 2 * np.pi, 3601) coords = np.vstack([max_x * np.cos(t), max_x * np.sin(t)])[:, ::-1] # update the projection boundary projection._boundary = LinearRing(coords.T)
def _xywh_to_ring(self, x, y, width, height): points = [(x - (width / 2.0), y - (height / 2.0)), (x - (width / 2.0), y + (height / 2.0)), (x + (width / 2.0), y + (height / 2.0)), (x + (width / 2.0), y - (height / 2.0)), (x - (width / 2.0), y - (height / 2.0))] return Polygon(LinearRing(points))
def get_ring(coords): '''tuple in format: west_lon, east_lon, south_lat, north_lat ''' west_lon, east_lon, south_lat, north_lat = coords lons_sq = [west_lon, west_lon, east_lon, east_lon] lats_sq = [north_lat, south_lat, south_lat, north_lat] ring = [LinearRing(list(zip(lons_sq, lats_sq)))] return ring
def test_linearring_from_unclosed_linestring(): coords = [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)] line = LineString(coords[:-1]) # Pass in unclosed line ring = LinearRing(line) assert len(ring.coords) == 4 assert ring.coords[:] == coords assert ring.geom_type == 'LinearRing'
def test_linearring_from_too_short_linestring(self): # Creation of LinearRing request at least 3 coordinates (unclosed) or # 4 coordinates (closed) coords = [(0.0, 0.0), (0.0, 0.0)] line = LineString(coords) with self.assertRaises(ValueError): LinearRing(line)
def _get_geometry(self, element): # Point, LineString, # Polygon, LinearRing if element.tag == ('%sPoint' % self.ns): coords = self._get_coordinates(element) self._get_geometry_spec(element) return Point(coords[0]) if element.tag == ('%sLineString' % self.ns): coords = self._get_coordinates(element) # issue seen with Garmin kml feeds with one coordinate linestrings if len(coords) < 2: logger.warn('LineStrings must have at least 2 coordinate tuples') return self._get_geometry_spec(element) return LineString(coords) if element.tag == ('%sPolygon' % self.ns): self._get_geometry_spec(element) outer_boundary = element.find('%souterBoundaryIs' % self.ns) ob = self._get_linear_ring(outer_boundary) inner_boundaries = element.findall('%sinnerBoundaryIs' % self.ns) ibs = [] for inner_boundary in inner_boundaries: ibs.append(self._get_linear_ring(inner_boundary)) return Polygon(ob, ibs) if element.tag == ('%sLinearRing' % self.ns): coords = self._get_coordinates(element) self._get_geometry_spec(element) return LinearRing(coords)
def intersections(self, a, b): """check if two polylines are intersected Args: a (polyline): polyline a b (polyline): polyline b Returns: boolean: true if polylines are intersected """ try: ea = LinearRing(a) eb = LinearRing(b) return ea.intersection(eb) except: return False
def _get_geometry(self, element): # Point, LineString, # Polygon, LinearRing if element.tag == ('%sPoint' % self.ns): coords = self._get_coordinates(element) self._get_geometry_spec(element) return Point(coords[0]) if element.tag == ('%sLineString' % self.ns): coords = self._get_coordinates(element) self._get_geometry_spec(element) return LineString(coords) if element.tag == ('%sPolygon' % self.ns): self._get_geometry_spec(element) outer_boundary = element.find('%souterBoundaryIs' % self.ns) ob = self._get_linear_ring(outer_boundary) inner_boundaries = element.findall('%sinnerBoundaryIs' % self.ns) ibs = [ self._get_linear_ring(inner_boundary) for inner_boundary in inner_boundaries ] return Polygon(ob, ibs) if element.tag == ('%sLinearRing' % self.ns): coords = self._get_coordinates(element) self._get_geometry_spec(element) return LinearRing(coords)
def build_clusters(self, events, time_interval): events = list(events) event_count = len(events) cluster_count = 0 if events: clustered_points, points = self.initialize_clusters(event_count, events) if len(points) < 3: return distances = pdist(points) results = fastcluster.linkage(distances) self.apply_results(results, clustered_points) self.cluster_builder.with_timestamp(time_interval.end).with_interval_seconds(time_interval.duration.seconds) for clustered_events in self.get_clustered_events(event_count, clustered_points): events_in_cluster = len(clustered_events) if events_in_cluster > 2: points = np.ndarray([events_in_cluster, 2]) for index, strike in enumerate(clustered_events): points[index][0] = strike.x points[index][1] = strike.y try: hull = ConvexHull(points) except QhullError: self.logger.error("".join(str(points).splitlines())) continue shape_points = [ [ round(points[vertex, 0], self.coordinate_precision), round(points[vertex, 1], self.coordinate_precision), ] for vertex in hull.vertices ] shape = LinearRing(shape_points) shape = shape.buffer(self.buffer_size) shape = shape.simplify(self.coordinate_accuracy, preserve_topology=False) shape = shape.exterior if shape is None: continue x_values = np.round(shape.coords.xy[0], self.coordinate_precision) y_values = np.round(shape.coords.xy[1], self.coordinate_precision) shape = LinearRing(zip(x_values, y_values)) cluster_count += 1 yield self.cluster_builder.with_strike_count(events_in_cluster).with_shape(shape).build() self.logger.debug( "build_clusters({} +{}): {} events -> {} clusters -> {} filtered".format( time_interval.start, time_interval.duration, event_count, len(clustered_points), cluster_count ) )
def fig_intersects(fig1, fig2) -> float: lr1 = LinearRing(fig1) lr2 = LinearRing(fig2) return 1 if lr1.intersects(lr2) else 0
def test_polygon(self): # Initialization # Linear rings won't usually be created by users, but by polygons coords = ((0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (1.0, 0.0)) ring = LinearRing(coords) self.assertEqual(len(ring.coords), 5) self.assertEqual(ring.coords[0], ring.coords[4]) self.assertEqual(ring.coords[0], ring.coords[-1]) self.assertTrue(ring.is_ring) # Coordinate modification ring.coords = ((0.0, 0.0), (0.0, 2.0), (2.0, 2.0), (2.0, 0.0)) self.assertEqual( ring.__geo_interface__, {'type': 'LinearRing', 'coordinates': ((0.0, 0.0), (0.0, 2.0), (2.0, 2.0), (2.0, 0.0), (0.0, 0.0))}) # Test ring adapter coords = [[0.0, 0.0], [0.0, 1.0], [1.0, 1.0], [1.0, 0.0]] ra = asLinearRing(coords) self.assertTrue(ra.wkt.upper().startswith('LINEARRING')) self.assertEqual(dump_coords(ra), [(0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (1.0, 0.0), (0.0, 0.0)]) coords[3] = [2.0, -1.0] self.assertEqual(dump_coords(ra), [(0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (2.0, -1.0), (0.0, 0.0)]) # Construct a polygon, exterior ring only polygon = Polygon(coords) self.assertEqual(len(polygon.exterior.coords), 5) # Ring Access self.assertIsInstance(polygon.exterior, LinearRing) ring = polygon.exterior self.assertEqual(len(ring.coords), 5) self.assertEqual(ring.coords[0], ring.coords[4]) self.assertEqual(ring.coords[0], (0., 0.)) self.assertTrue(ring.is_ring) self.assertEqual(len(polygon.interiors), 0) # Create a new polygon from WKB data = polygon.wkb polygon = None ring = None polygon = load_wkb(data) ring = polygon.exterior self.assertEqual(len(ring.coords), 5) self.assertEqual(ring.coords[0], ring.coords[4]) self.assertEqual(ring.coords[0], (0., 0.)) self.assertTrue(ring.is_ring) polygon = None # Interior rings (holes) polygon = Polygon(coords, [((0.25, 0.25), (0.25, 0.5), (0.5, 0.5), (0.5, 0.25))]) self.assertEqual(len(polygon.exterior.coords), 5) self.assertEqual(len(polygon.interiors[0].coords), 5) with self.assertRaises(IndexError): # index out of range polygon.interiors[1] # Test from another Polygon copy = Polygon(polygon) self.assertEqual(len(polygon.exterior.coords), 5) self.assertEqual(len(polygon.interiors[0].coords), 5) with self.assertRaises(IndexError): # index out of range polygon.interiors[1] # Coordinate getters and setters raise exceptions self.assertRaises(NotImplementedError, polygon._get_coords) with self.assertRaises(NotImplementedError): polygon.coords # Geo interface self.assertEqual( polygon.__geo_interface__, {'type': 'Polygon', 'coordinates': (((0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (2.0, -1.0), (0.0, 0.0)), ((0.25, 0.25), (0.25, 0.5), (0.5, 0.5), (0.5, 0.25), (0.25, 0.25)))}) # Adapter hole_coords = [((0.25, 0.25), (0.25, 0.5), (0.5, 0.5), (0.5, 0.25))] pa = asPolygon(coords, hole_coords) self.assertEqual(len(pa.exterior.coords), 5) self.assertEqual(len(pa.interiors), 1) self.assertEqual(len(pa.interiors[0].coords), 5) # Test Non-operability of Null rings r_null = LinearRing() self.assertEqual(r_null.wkt, 'GEOMETRYCOLLECTION EMPTY') self.assertEqual(r_null.length, 0.0) # Check that we can set coordinates of a null geometry r_null.coords = [(0, 0), (1, 1), (1, 0)] self.assertAlmostEqual(r_null.length, 3.414213562373095) # Error handling with self.assertRaises(ValueError): # A LinearRing must have at least 3 coordinate tuples Polygon([[1, 2], [2, 3]])