Yes, there are several other geospatial file formats that you might consider supporting, depending on your use case and the needs of your users. Here are some common ones:
KML (Keyhole Markup Language): Often used with Google Earth.
GML (Geography Markup Language): An XML-based format for geographic data.
GPX (GPS Exchange Format): Commonly used for GPS data.
CSV with WKT (Well-Known Text): A simple text format for representing vector geometry objects.
GeoTIFF: A format for raster data.
WFS (Web Feature Service): A standard for serving geospatial features over the web.
Here's how you can extend your ingest_file_to_db function to support KML and CSV with WKT, in addition to GeoJSON and Shapefiles:
import json
import tempfile
import zipfile
import fiona
import csv
from pathlib import Path
from pyproj import CRS, Transformer
from django.contrib.gis.geos import GEOSGeometry, Point, Polygon, LineString
from .models import Feature, Property, Layer, User
from fastkml import kml
def ingest_file_to_db(gcs_path, layer_name, user_email):
"""Ingests a file into the specified database table."""
with tempfile.NamedTemporaryFile(delete=True) as temp_file:
download_from_gcs(gcs_path, temp_file.name)
temp_file.seek(0) # Ensure the file pointer is at the beginning
# Determine the file type
file_extension = Path(gcs_path).suffix.lower()
if file_extension == '.geojson':
# Read the GeoJSON file
geojson_data = json.load(temp_file)
features = geojson_data["features"]
elif file_extension == '.zip':
# Handle shapefile in a .zip file
with zipfile.ZipFile(temp_file, 'r') as zip_ref:
zip_ref.extractall(temp_file.name)
shapefile_path = next(
(Path(temp_file.name) / name for name in zip_ref.namelist() if name.endswith('.shp')),
None
)
if shapefile_path is None:
raise ValueError("No shapefile found in the .zip archive")
with fiona.open(shapefile_path) as shapefile:
crs = CRS(shapefile.crs)
transformer = Transformer.from_crs(crs, CRS("EPSG:4326"), always_xy=True)
features = []
for feature in shapefile:
geom = feature["geometry"]
if crs != CRS("EPSG:4326"):
geom = transform_geom(transformer, geom)
features.append({
"type": "Feature",
"geometry": geom,
"properties": feature["properties"]
})
elif file_extension == '.kml':
# Handle KML file
kml_data = kml.KML()
kml_data.from_string(temp_file.read().decode('utf-8'))
features = []
for feature in kml_data.features():
geom = feature.geometry
if geom.crs != CRS("EPSG:4326"):
transformer = Transformer.from_crs(geom.crs, CRS("EPSG:4326"), always_xy=True)
geom = transform_geom(transformer, geom)
features.append({
"type": "Feature",
"geometry": geom.__geo_interface__,
"properties": feature.extended_data
})
elif file_extension == '.csv':
# Handle CSV with WKT
features = []
reader = csv.DictReader(temp_file)
for row in reader:
wkt = row.pop('wkt')
geom = GEOSGeometry(wkt)
if geom.srid != 4326:
geom.transform(4326)
features.append({
"type": "Feature",
"geometry": json.loads(geom.geojson),
"properties": row
})
else:
raise ValueError("Unsupported file type")
user = User.objects.get(email=user_email)
# Create or get the Layer instance
layer, created = Layer.objects.get_or_create(name=layer_name, user=user)
# Create Feature instances
feature_instances = [
Feature(
name=feature["properties"].get("name", "Unnamed Feature"),
geometry=GEOSGeometry(json.dumps(feature["geometry"])),
layer=layer,
)
for feature in features
]
# Bulk create Feature instances
Feature.objects.bulk_create(feature_instances)
# Create Property instances
property_instances = [
Property(key=key, value=value, feature=feature_instance)
for feature_instance, feature in zip(feature_instances, features)
for key, value in feature["properties"].items()
]
# Bulk create Property instances
Property.objects.bulk_create(property_instances)
return layer.id
def transform_geom(transformer, geom):
"""Transforms the geometry to EPSG:4326."""
if geom["type"] == "Point":
x, y = transformer.transform(geom["coordinates"][0], geom["coordinates"][1])
return {"type": "Point", "coordinates": [x, y]}
elif geom["type"] == "LineString":
coords = [transformer.transform(x, y) for x, y in geom["coordinates"]]
return {"type": "LineString", "coordinates": coords}
elif geom["type"] == "Polygon":
coords = [[transformer.transform(x, y) for x, y in ring] for ring in geom["coordinates"]]
return {"type": "Polygon", "coordinates": coords}
# Add support for other geometry types as needed
else:
raise ValueError(f"Unsupported geometry type: {geom['type']}")
From copilot