def test_is_feature_collection(self): self.assertTrue(GeoJSON.is_feature_collection(dict(type='FeatureCollection', features=[dict(type='Feature', geometry=dict(type='Point', coordinates=[ 2.13, 42.2]))]))) self.assertFalse(GeoJSON.is_feature_collection(dict(type='Point', coordinates=[2.13, 42.2])))
def test_get_type_name(self): self.assertEqual('Feature', GeoJSON.get_type_name(dict(type='Feature'))) self.assertEqual(None, GeoJSON.get_type_name(dict(pype='Feature'))) self.assertEqual(None, GeoJSON.get_type_name(dict())) self.assertEqual(None, GeoJSON.get_type_name(17))
def get_time_series_for_feature_collection(ctx: ServiceContext, ds_name: str, var_name: str, feature_collection: Dict, start_date: np.datetime64 = None, end_date: np.datetime64 = None, include_count: bool = False, include_stdev: bool = False, max_valids: int = None) -> Dict: """ Get the time-series for the geometries of a given *feature_collection*. :param ctx: Service context object :param ds_name: The dataset identifier. :param var_name: The variable name. :param feature_collection: The feature collection. :param start_date: An optional start date. :param end_date: An optional end date. :param include_count: Whether to include the valid number of observations in the result. :param include_stdev: Whether to include the standard deviation in the result. :param max_valids: Optional number of valid points. If it is None (default), also missing values are returned as NaN; if it is -1 only valid values are returned; if it is a positive integer, the most recent valid values are returned. :return: Time-series data structure. """ dataset = ctx.get_time_series_dataset(ds_name, var_name=var_name) features = GeoJSON.get_feature_collection_features(feature_collection) if features is None: raise ServiceBadRequestError("Invalid GeoJSON feature collection") shapes = [] for feature in features: geometry = GeoJSON.get_feature_geometry(feature) try: geometry = shapely.geometry.shape(geometry) except (TypeError, ValueError) as e: raise ServiceBadRequestError( "Invalid GeoJSON feature collection") from e shapes.append(geometry) with measure_time() as time_result: result = _get_time_series_for_geometries(dataset, var_name, shapes, start_date=start_date, end_date=end_date, include_count=include_count, include_stdev=include_stdev, max_valids=max_valids) if ctx.trace_perf: LOG.info( f'get_time_series_for_feature_collection: dataset id {ds_name}, variable {var_name},' f'size={len(result["results"])}, took {time_result.duration} seconds' ) return result
def test_get_feature_geometry(self): self.assertEqual(dict(type='Point', coordinates=[2.13, 42.2]), GeoJSON.get_feature_geometry(dict(type='Feature', geometry=dict(type='Point', coordinates=[2.13, 42.2])))) self.assertEqual(None, GeoJSON.get_feature_geometry(dict(type='Pleature', geometry=dict(type='Point', coordinates=[2.13, 42.2])))) self.assertEqual(None, GeoJSON.get_feature_geometry(dict(type='Feature', geometry=dict(type='Point')))) self.assertEqual(None, GeoJSON.get_feature_geometry(dict(type='Feature', geometry=17)))
def convert_geometry( geometry: Optional[Geometry] ) -> Optional[shapely.geometry.base.BaseGeometry]: if isinstance(geometry, shapely.geometry.base.BaseGeometry): return geometry if isinstance(geometry, dict): if not GeoJSON.is_geometry(geometry): raise ValueError(_INVALID_GEOMETRY_MSG) return shapely.geometry.shape(geometry) if isinstance(geometry, str): return shapely.wkt.loads(geometry) if geometry is None: return None # noinspection PyBroadException try: x1, y1, x2, y2 = geometry return shapely.geometry.shape( dict(type='Polygon', coordinates=[[[x1, y1], [x2, y1], [x2, y2], [x1, y2], [x1, y1]]])) except Exception: # noinspection PyBroadException try: x, y = geometry return shapely.geometry.shape( dict(type='Point', coordinates=[x, y])) except Exception: pass raise ValueError(_INVALID_GEOMETRY_MSG)
def test_is_geometry(self): self.assertTrue(GeoJSON.is_geometry(dict(type='Point', coordinates=[2.13, 42.2]))) self.assertTrue(GeoJSON.is_geometry(dict(type='Point', coordinates=None))) self.assertFalse(GeoJSON.is_geometry(dict(type='Point'))) self.assertTrue(GeoJSON.is_geometry(dict(type='GeometryCollection', geometries=None))) self.assertTrue(GeoJSON.is_geometry(dict(type='GeometryCollection', geometries=[]))) self.assertFalse(GeoJSON.is_geometry(dict(type='GeometryCollection'))) self.assertFalse(GeoJSON.is_geometry(dict(type='Feature', properties=None)))
def get_time_series_for_geometry(ctx: ServiceContext, ds_name: str, var_name: str, geometry: Dict, start_date: np.datetime64 = None, end_date: np.datetime64 = None, include_count: bool = False, include_stdev: bool = False, max_valids: int = None) -> Dict: """ Get the time-series for a given *geometry*. :param ctx: Service context object :param ds_name: The dataset identifier. :param var_name: The variable name. :param geometry: The geometry, usually a point or polygon. :param start_date: An optional start date. :param end_date: An optional end date. :param include_count: Whether to include the valid number of observations in the result. :param include_stdev: Whether to include the standard deviation in the result. :param max_valids: Optional number of valid points. If it is None (default), also missing values are returned as NaN; if it is -1 only valid values are returned; if it is a positive integer, the most recent valid values are returned. :return: Time-series data structure. """ dataset = ctx.get_time_series_dataset(ds_name, var_name=var_name) if not GeoJSON.is_geometry(geometry): raise ServiceBadRequestError("Invalid GeoJSON geometry") if isinstance(geometry, dict): geometry = shapely.geometry.shape(geometry) with measure_time() as time_result: result = _get_time_series_for_geometry(dataset, var_name, geometry, start_date=start_date, end_date=end_date, include_count=include_count, include_stdev=include_stdev, max_valids=max_valids) if ctx.trace_perf: LOG.info( f'get_time_series_for_geometry: dataset id {ds_name}, variable {var_name}, ' f'geometry type {geometry},' f'size={len(result["results"])}, took {time_result.duration} seconds' ) return result
def get_time_series_for_geometry_collection(ctx: ServiceContext, ds_name: str, var_name: str, geometry_collection: Dict, start_date: np.datetime64 = None, end_date: np.datetime64 = None, include_count: bool = False, include_stdev: bool = False, max_valids: int = None) -> Dict: """ Get the time-series for a given *geometry_collection*. :param ctx: Service context object :param ds_name: The dataset identifier. :param var_name: The variable name. :param geometry_collection: The geometry collection. :param start_date: An optional start date. :param end_date: An optional end date. :param include_count: Whether to include the valid number of observations in the result. :param include_stdev: Whether to include the standard deviation in the result. :param max_valids: Optional number of valid points. If it is None (default), also missing values are returned as NaN; if it is -1 only valid values are returned; if it is a positive integer, the most recent valid values are returned. :return: Time-series data structure. """ dataset = _get_time_series_dataset(ctx, ds_name, var_name) geometries = GeoJSON.get_geometry_collection_geometries( geometry_collection) if geometries is None: raise ServiceBadRequestError("Invalid GeoJSON geometry collection") shapes = [] for geometry in geometries: try: geometry = shapely.geometry.shape(geometry) except (TypeError, ValueError) as e: raise ServiceBadRequestError( "Invalid GeoJSON geometry collection") from e shapes.append(geometry) return _get_time_series_for_geometries(dataset, var_name, shapes, start_date=start_date, end_date=end_date, include_count=include_count, include_stdev=include_stdev, max_valids=max_valids)
def _to_geo_json_geometries( geo_json: GeoJsonObj) -> Tuple[List[GeoJsonGeometry], bool]: is_collection = False if GeoJSON.is_feature(geo_json): geometry = _get_feature_geometry(geo_json) geometries = [geometry] elif GeoJSON.is_feature_collection(geo_json): is_collection = True features = GeoJSON.get_feature_collection_features(geo_json) geometries = [_get_feature_geometry(feature) for feature in features] if features else [] elif GeoJSON.is_geometry_collection(geo_json): is_collection = True geometries = GeoJSON.get_geometry_collection_geometries(geo_json) elif GeoJSON.is_geometry(geo_json): geometries = [geo_json] else: raise ServiceBadRequestError("GeoJSON object expected") return geometries, is_collection
def _get_feature_geometry(feature: GeoJsonFeature) -> GeoJsonGeometry: geometry = GeoJSON.get_feature_geometry(feature) if geometry is None or not GeoJSON.is_geometry(geometry): raise ServiceBadRequestError("GeoJSON feature without geometry") return geometry
def convert_geometry( geometry: Optional[GeometryLike] ) -> Optional[shapely.geometry.base.BaseGeometry]: """ Convert a geometry-like object into a shapely geometry object (``shapely.geometry.BaseGeometry``). A geometry-like object is may be any shapely geometry object, * a dictionary that can be serialized to valid GeoJSON, * a WKT string, * a box given by a string of the form "<x1>,<y1>,<x2>,<y2>" or by a sequence of four numbers x1, y1, x2, y2, * a point by a string of the form "<x>,<y>" or by a sequence of two numbers x, y. Handling of geometries crossing the antimeridian: * If box coordinates are given, it is allowed to pass x1, x2 where x1 > x2, which is interpreted as a box crossing the antimeridian. In this case the function splits the box along the antimeridian and returns a multi-polygon. * In all other cases, 2D geometries are assumed to _not cross the antimeridian at all_. :param geometry: A geometry-like object :return: Shapely geometry object or None. """ if isinstance(geometry, shapely.geometry.base.BaseGeometry): return geometry if isinstance(geometry, dict): if GeoJSON.is_geometry(geometry): return shapely.geometry.shape(geometry) elif GeoJSON.is_feature(geometry): geometry = GeoJSON.get_feature_geometry(geometry) if geometry is not None: return shapely.geometry.shape(geometry) elif GeoJSON.is_feature_collection(geometry): features = GeoJSON.get_feature_collection_features(geometry) if features is not None: geometries = [ f2 for f2 in [GeoJSON.get_feature_geometry(f1) for f1 in features] if f2 is not None ] if geometries: geometry = dict(type='GeometryCollection', geometries=geometries) return shapely.geometry.shape(geometry) raise ValueError(_INVALID_GEOMETRY_MSG) if isinstance(geometry, str): return shapely.wkt.loads(geometry) if geometry is None: return None invalid_box_coords = False # noinspection PyBroadException try: x1, y1, x2, y2 = geometry is_point = x1 == x2 and y1 == y2 if is_point: return shapely.geometry.Point(x1, y1) invalid_box_coords = x1 == x2 or y1 >= y2 if not invalid_box_coords: return get_box_split_bounds_geometry(x1, y1, x2, y2) except Exception: # noinspection PyBroadException try: x, y = geometry return shapely.geometry.Point(x, y) except Exception: pass if invalid_box_coords: raise ValueError(_INVALID_BOX_COORDS_MSG) raise ValueError(_INVALID_GEOMETRY_MSG)
def test_is_point(self): self.assertTrue(GeoJSON.is_point(dict(type='Point', coordinates=[2.13, 42.2]))) self.assertFalse(GeoJSON.is_point(dict(type='Feature', properties=None)))
def test_is_feature(self): self.assertTrue(GeoJSON.is_feature(dict(type='Feature', geometry=dict(type='Point', coordinates=[2.13, 42.2])))) self.assertFalse(GeoJSON.is_feature(dict(type='Point', coordinates=[2.13, 42.2])))