diff --git a/geospatial/streamlit-apps/H3-SiS/basic_h3.py b/geospatial/streamlit-apps/H3-SiS/basic_h3.py index 30bd785..2e1828b 100644 --- a/geospatial/streamlit-apps/H3-SiS/basic_h3.py +++ b/geospatial/streamlit-apps/H3-SiS/basic_h3.py @@ -1,3 +1,7 @@ +# This code of the Streamlit app visualizes data from the following dataset: +# https://app.snowflake.com/marketplace/listing/GZSVZ8ON6J/dataconsulting-pl-opencellid-open-database-of-cell-towers?search=opencellid +# The prerequisite for this app is to install that free dataset from the Marketplace to the 'opencellid' database + import streamlit as st import pydeck as pdk from snowflake.snowpark.context import get_active_session diff --git a/geospatial/streamlit-apps/H3-sample/main.py b/geospatial/streamlit-apps/H3-sample/main.py index 41ce2ab..8c64c94 100644 --- a/geospatial/streamlit-apps/H3-sample/main.py +++ b/geospatial/streamlit-apps/H3-sample/main.py @@ -1,3 +1,6 @@ +# This code of the Streamlit app visualizes data from the following dataset: +# https://app.snowflake.com/marketplace/listing/GZSVZ8ON6J/dataconsulting-pl-opencellid-open-database-of-cell-towers + import streamlit as st import pandas as pd import pydeck as pdk diff --git a/samples/geospatial/Python UDFs/PY_COLLECTION_TO_POLYGONS.sql b/samples/geospatial/Python UDFs/PY_COLLECTION_TO_POLYGONS.sql new file mode 100644 index 0000000..c78f691 --- /dev/null +++ b/samples/geospatial/Python UDFs/PY_COLLECTION_TO_POLYGONS.sql @@ -0,0 +1,40 @@ +CREATE OR REPLACE FUNCTION PY_COLLECTION_TO_POLYGONS(geo geography) +returns geography +language python +runtime_version = 3.8 +packages = ('shapely') +handler = 'udf' +AS $$ +from shapely import wkt +from shapely.ops import unary_union +from shapely.validation import make_valid +from shapely.geometry import shape, mapping +def udf(geo): + if not geo: + return None + try: + collection = shape(geo) + collection = make_valid(collection) if not collection.is_valid else collection + + # Filter out non-Polygon/MultiPolygon geometries and flatten nested GeometryCollections + def flatten_geometries(geometry): + if geometry.geom_type in ["Polygon", "MultiPolygon"]: + return [geometry] + elif geometry.geom_type == "GeometryCollection": + return [sub_geom for geo in geometry.geoms for sub_geom in flatten_geometries(geo)] + else: + return [] + geometries_checked = flatten_geometries(collection) + if not geometries_checked: + return None + dest_shape = unary_union(geometries_checked) + valid_shape = make_valid(dest_shape) if not dest_shape.is_valid else dest_shape + return mapping(valid_shape) + except Exception as e: + # Logging the exception 'e' for debugging purposes + return e +$$; + +select PY_COLLECTION_TO_POLYGONS(to_geography('GEOMETRYCOLLECTION(POINT(-91.72073364257811 41.77182378456081), + POINT(-96.57995223999022 37.66099365286695),POLYGON((-86.36249542236327 40.02077884854819,-84.54580307006836 36.46298689866559, + -90.70569992065428 36.01300517087813,-86.36249542236327 40.02077884854819)))')) \ No newline at end of file diff --git a/samples/geospatial/Python UDFs/PY_LOAD_GEOFILES.sql b/samples/geospatial/Python UDFs/PY_LOAD_GEOFILES.sql index 3a88029..b379b6c 100644 --- a/samples/geospatial/Python UDFs/PY_LOAD_GEOFILES.sql +++ b/samples/geospatial/Python UDFs/PY_LOAD_GEOFILES.sql @@ -19,8 +19,7 @@ -- parameters: -- - Snowflake share of where the file is located -- - Name of the file, including path --- - GEOFILE_NAME - The name of the GEO file inside the package zip that needs to --- loaded +-- - GEOFILE_NAME - The name of the GEO file inside the package zip that needs to be loaded -- === UDTF Code === CREATE OR REPLACE FUNCTION PY_LOAD_GEOFILE(PATH_TO_FILE string, filename string) @@ -33,6 +32,7 @@ AS $$ from shapely.geometry import shape from snowflake.snowpark.files import SnowflakeFile from fiona.io import ZipMemoryFile +import fiona class GeoFileReader: def process(self, PATH_TO_FILE: str, filename: str): fiona.drvsupport.supported_drivers['libkml'] = 'rw' @@ -49,20 +49,20 @@ $$; create or replace table GEOLAB.GEOGRAPHY.TABLE_NAME as SELECT properties:Field_1::string as field_1, properties:Field_2::string as Field_2, -to_geography(wkb, True) as geometry FROM table(PY_LOAD_GEOFILE(build_scoped_file_url(@stage_name, 'archive_name.zip'), 'file_name.shp')); +to_geography(wkb, True) as geometry FROM table(PY_LOAD_GEOFILE(build_scoped_file_url(@stage_name, 'ZIP_FILE_NAME.zip'), 'GEOFILE_NAME.shp')); -- === Example execution (MapInfo TAB File) === create or replace table GEOLAB.GEOGRAPHY.TABLE_NAME as SELECT properties:Field_1::string as field_1, properties:Field_2::string as Field_2, -to_geography(wkb, True) as geometry FROM table(PY_LOAD_GEOFILE(build_scoped_file_url(@stage_name, 'archive_name.zip'), 'file_name.tab')); +to_geography(wkb, True) as geometry FROM table(PY_LOAD_GEOFILE(build_scoped_file_url(@stage_name, 'ZIP_FILE_NAME.zip'), 'GEOFILE_NAME.tab')); -- === Example execution (Google Earth KML File) === select to_geography(wkb, True) as geometry, properties:Name::string as Name, properties:altitudeMode::string as altitudeMode -from table(PY_LOAD_GEOFILE(build_scoped_file_url(@tmobile, 'archive_name.zip'), 'file_name.kml')); +from table(PY_LOAD_GEOFILE(build_scoped_file_url(@tmobile, 'ZIP_FILE_NAME.zip'), 'GEOFILE_NAME.kml')); -- === Example execution (OGC GeoPackage) === -- Note: A specific layer in the .gpkg file is opened by specifying layer name in UDTF `with zip.open(filename,layer=layername) as collection:` @@ -70,4 +70,4 @@ SELECT properties:Field_1::string as field_1, properties:Field_2::string as Field_2, to_geography(wkb, True) as geometry -FROM table(PY_LOAD_GEOFILE(build_scoped_file_url(@stage_name, 'archive_name.zip'), 'file_name.gpkg')); \ No newline at end of file +FROM table(PY_LOAD_GEOFILE(build_scoped_file_url(@stage_name, 'ZIP_FILE_NAME.zip'), 'GEOFILE_NAME.gpkg')); diff --git a/samples/geospatial/Python UDFs/PY_LOAD_MULTIPLE_SHAPEFILES.sql b/samples/geospatial/Python UDFs/PY_LOAD_MULTIPLE_SHAPEFILES.sql index ba40aa5..60c15ad 100644 --- a/samples/geospatial/Python UDFs/PY_LOAD_MULTIPLE_SHAPEFILES.sql +++ b/samples/geospatial/Python UDFs/PY_LOAD_MULTIPLE_SHAPEFILES.sql @@ -13,11 +13,11 @@ -- required the timeout to be increased to over 10 minutes. -- === Parameters === --- - ZIP_FILE_NAME - The scoped URL to the zipped file on the Snowflake share and is +-- - PATH_TO_FILE - The scoped URL to the zipped file on the Snowflake share and is -- generated using the build_scoped_file_url function. This function requires two -- parameters: --- - Snowflake share of where the file is located --- - Name of the file, including path +-- - @stage_name - Snowflake stage of where the file is located +-- - ZIP_FILE_NAME - Name of the file, including path -- === UDTF Code === CREATE OR REPLACE FUNCTION SHAPE_MULTI_FILE(PATH_TO_FILE string) diff --git a/samples/geospatial/Python UDFs/ST_INTESECTION.sql b/samples/geospatial/Python UDFs/ST_INTESECTION.sql deleted file mode 100644 index 81f266f..0000000 --- a/samples/geospatial/Python UDFs/ST_INTESECTION.sql +++ /dev/null @@ -1,18 +0,0 @@ -CREATE OR REPLACE FUNCTION PY_INTERSECTION(g1 geometry, g2 geometry) -returns geometry -language python -runtime_version = 3.8 -packages = ('shapely') -handler = 'udf' -AS $$ -import shapely -from shapely.geometry import shape, mapping -def udf(g1, g2): - shape1 = shape(g1) - shape2 = shape(g2) - return mapping(shape1.intersection(shape2)) -$$; - - -select py_intersection(to_geometry('POLYGON((-97.72482633590697 38.04944182145127,-84.24133479595181 38.03929418732139,-91.34215861558913 31.296177449497677,-97.72482633590697 38.04944182145127))'), - to_geometry('POLYGON((-91.79851233959197 36.206334232169056,-97.41053044795987 31.046008090324193,-83.82624685764311 30.701652827539803,-91.79851233959197 36.206334232169056))')); \ No newline at end of file diff --git a/samples/geospatial/Python UDFs/ST_MAKELINE.sql b/samples/geospatial/Python UDFs/ST_MAKELINE.sql deleted file mode 100644 index d16aa59..0000000 --- a/samples/geospatial/Python UDFs/ST_MAKELINE.sql +++ /dev/null @@ -1,17 +0,0 @@ -CREATE OR REPLACE FUNCTION PY_MAKELINE(g1 array) -returns geography -language python -runtime_version = 3.8 -packages = ('geopandas','shapely') -handler = 'udf' -AS $$ -import geopandas as gpd -from shapely.geometry import Point, LineString -from shapely.geometry import shape, mapping -def udf(g1): - geo_object = gpd.GeoSeries([shape(i) for i in g1]) - final_shape = LineString(geo_object.tolist()) - return mapping(final_shape) -$$; - -SELECT PY_MAKELINE((ARRAY_AGG(st_asgeojson(geo))) FROM OBJ_OF_INTEREST_SO; \ No newline at end of file diff --git a/samples/geospatial/Python UDFs/ST_TRANSFORM.sql b/samples/geospatial/Python UDFs/ST_TRANSFORM.sql deleted file mode 100644 index 0dfb1d8..0000000 --- a/samples/geospatial/Python UDFs/ST_TRANSFORM.sql +++ /dev/null @@ -1,24 +0,0 @@ -CREATE OR REPLACE FUNCTION PY_TRANSFORM(g1 GEOMETRY, srid_from NUMBER, srid_to NUMBER) -returns geometry -language python -runtime_version = 3.8 -packages = ('pyproj','shapely') -handler = 'udf' -AS $$ -import pyproj -import shapely -from shapely.ops import transform -from shapely.geometry import shape -from shapely import wkb, wkt -def udf(g1, srid_from, srid_to): - source_srid = pyproj.CRS(srid_from) - target_srid = pyproj.CRS(srid_to) - project = pyproj.Transformer.from_crs(source_srid, target_srid, always_xy=True).transform - shape_wgs = shape(g1) - shape_tr = transform(project, shape_wgs) - return shapely.geometry.mapping(shape_tr) -$$; - - -SELECT ST_SETSRID(PY_TRANSFORM(to_geometry('POLYGON((-97.26697593927382 37.93877176927013,-100.04266262054442 34.33784839156256,-88.28472465276718 34.20582640497908,-97.26697593927382 37.93877176927013))'), 4326, 3443), 3443) as transformed_shape; - diff --git a/samples/geospatial/Python UDFs/ST_UNION_AGG.sql b/samples/geospatial/Python UDFs/ST_UNION_AGG.sql deleted file mode 100644 index 64d824f..0000000 --- a/samples/geospatial/Python UDFs/ST_UNION_AGG.sql +++ /dev/null @@ -1,18 +0,0 @@ -CREATE OR REPLACE FUNCTION PY_UNION_AGG(g1 array) -returns geography -language python -runtime_version = 3.8 -packages = ('geopandas','shapely') -handler = 'udf' -AS $$ -import geopandas as gpd -from shapely.ops import unary_union -from shapely.geometry import shape, mapping -def udf(g1): - geo_object = gpd.GeoSeries([shape(i) for i in g1]) - shape_union = unary_union(geo_object) - return mapping(shape_union) -$$; - - -SELECT PY_UNION_AGG(ARRAY_AGG(st_asgeojson(geo))) FROM OBJ_OF_INTEREST_SO; \ No newline at end of file