aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--config.yml81
-rw-r--r--export_mvt.py110
2 files changed, 174 insertions, 17 deletions
diff --git a/config.yml b/config.yml
index 744122a..a9d1f85 100644
--- a/config.yml
+++ b/config.yml
@@ -2709,7 +2709,10 @@ layers:
- AvvSasong
- AvvHa
- AvverkningsanmalanKlass
- publish: anmald
+ publish:
+ anmald:
+ fields:
+ age: Inkomdatum
'sks:avverk_utford':
# https://geodpags.skogsstyrelsen.se/geodataport/feeds/UtfordAvverk.xml
@@ -2799,7 +2802,10 @@ layers:
Beteckn:
- replace: 'Visas ej'
with: null
- publish: utford
+ publish:
+ utford:
+ fields:
+ age: Avvdatum
'sametinget:betesomrade':
description: 'Samebyarnas betesområden: Renbetesområden'
@@ -3217,30 +3223,44 @@ layers:
minzoom: 4
where: |
"Raderat" IS FALSE AND "Statuskod" = 4
+ fields:
+ age: SenasteUppdaterat
station_processed:
minzoom: 4
where: |
"Raderat" IS FALSE AND "Statuskod" = 1
+ fields:
+ age: SenasteUppdaterat
station_approved:
minzoom: 4
where: |
"Raderat" IS FALSE AND "Statuskod" = 3
+ fields:
+ age: SenasteUppdaterat
station_revoked:
minzoom: 4
where: |
"Raderat" IS FALSE AND ("Statuskod" = 6 OR "Statuskod" = 9)
+ fields:
+ age: SenasteUppdaterat
station_rejected:
minzoom: 4
where: |
"Raderat" IS FALSE AND "Statuskod" = 7
+ fields:
+ age: SenasteUppdaterat
station_dismounted:
minzoom: 4
where: |
"Raderat" IS FALSE AND "Statuskod" = 5
+ fields:
+ age: SenasteUppdaterat
station_appealed:
minzoom: 4
where: |
"Raderat" IS FALSE AND "Statuskod" = 8
+ fields:
+ age: SenasteUppdaterat
'vbk:projekteringsomraden':
description: Vindbrukskollen landbaserade projekteringsområden (Länsstyrelsen)
@@ -3395,9 +3415,13 @@ layers:
area_current:
where: |
"Raderat" IS FALSE AND "EjAktuell" IS FALSE
+ fields:
+ age: SenasteUppdaterat
area_notcurrent:
where: |
"Raderat" IS FALSE AND "EjAktuell" IS NOT FALSE
+ fields:
+ age: SenasteUppdaterat
'vbk:havsbaserad_vindkraft':
description: Vindbrukskollen havsbaserad vindkraft (Länsstyrelsen)
@@ -3613,30 +3637,48 @@ layers:
offshore_completed:
where: |
"Raderat" IS FALSE AND "Projektstatus" = 'Uppförd'
+ fields:
+ age: SenasteUppdaterat
offshore_approved:
where: |
"Raderat" IS FALSE AND "Projektstatus" = 'Tillståndsansökan beviljad'
+ fields:
+ age: SenasteUppdaterat
offshore_amended:
where: |
"Raderat" IS FALSE AND "Projektstatus" = 'Ändringsansökan'
+ fields:
+ age: SenasteUppdaterat
offshore_rejected:
where: |
"Raderat" IS FALSE AND "Projektstatus" = 'Tillståndsansökan avslagen'
+ fields:
+ age: SenasteUppdaterat
offshore_appealed:
where: |
"Raderat" IS FALSE AND "Projektstatus" = 'Överklagad'
+ fields:
+ age: SenasteUppdaterat
offshore_applied:
where: |
"Raderat" IS FALSE AND "Projektstatus" = 'Tillståndsansökan inlämnad'
+ fields:
+ age: SenasteUppdaterat
offshore_consultation:
where: |
"Raderat" IS FALSE AND "Projektstatus" = 'Samråd inför tillståndsansökan'
+ fields:
+ age: SenasteUppdaterat
offshore_investigation:
where: |
"Raderat" IS FALSE AND "Projektstatus" = 'Inledande undersökningar'
+ fields:
+ age: SenasteUppdaterat
offshore_revoked:
where: |
"Raderat" IS FALSE AND "Projektstatus" = 'Inte aktuell eller återkallad'
+ fields:
+ age: SenasteUppdaterat
'mrr:ut_metaller_industrimineral_ansokta':
# https://resource.sgu.se/dokument/produkter/mineralrattigheter-beskrivning.pdf
@@ -3678,7 +3720,10 @@ layers:
path: mineralrattigheter.gpkg
format: GPKG
layername: ut_metaller_industrimineral_ansokta
- publish: appl_met
+ publish:
+ appl_met:
+ fields:
+ age: appl_date
'mrr:ut_diamant_ansokta':
# https://resource.sgu.se/dokument/produkter/mineralrattigheter-beskrivning.pdf
@@ -3720,7 +3765,10 @@ layers:
path: mineralrattigheter.gpkg
format: GPKG
layername: ut_diamant_ansokta
- publish: appl_ogd
+ publish:
+ appl_ogd:
+ fields:
+ age: appl_date
'mrr:bearbetningskoncessioner_ansokta':
# https://resource.sgu.se/dokument/produkter/mineralrattigheter-beskrivning.pdf
@@ -3762,7 +3810,10 @@ layers:
path: mineralrattigheter.gpkg
format: GPKG
layername: bearbetningskoncessioner_ansokta
- publish: appl_ec
+ publish:
+ appl_ec:
+ fields:
+ age: appl_date
'mrr:markanvisningar_bk_ansokta':
# https://resource.sgu.se/dokument/produkter/mineralrattigheter-beskrivning.pdf
@@ -3861,7 +3912,10 @@ layers:
licenceid:
- replace: '-'
with: null
- publish: appr_met
+ publish:
+ appr_met:
+ fields:
+ age: dec_date
'mrr:ut_diamant_beviljade':
# https://resource.sgu.se/dokument/produkter/mineralrattigheter-beskrivning.pdf
@@ -3924,7 +3978,10 @@ layers:
licenceid:
- replace: '-'
with: null
- publish: appr_ogd
+ publish:
+ appr_ogd:
+ fields:
+ age: dec_date
'mrr:bearbetningskoncessioner_beviljade':
# https://resource.sgu.se/dokument/produkter/mineralrattigheter-beskrivning.pdf
@@ -3988,7 +4045,10 @@ layers:
licenceid:
- replace: '-'
with: null
- publish: appr_ec
+ publish:
+ appr_ec:
+ fields:
+ age: dec_date
'mrr:markanvisningar_bk_beviljade':
# https://resource.sgu.se/dokument/produkter/mineralrattigheter-beskrivning.pdf
@@ -4038,7 +4098,10 @@ layers:
licenceid:
- replace: '-'
with: null
- publish: appr_dl
+ publish:
+ appr_dl:
+ fields:
+ age: dec_date
'mrr:ut_metaller_industrimineral_forbud':
# https://resource.sgu.se/dokument/produkter/mineralrattigheter-beskrivning.pdf
diff --git a/export_mvt.py b/export_mvt.py
index d19909c..31c7044 100644
--- a/export_mvt.py
+++ b/export_mvt.py
@@ -121,6 +121,7 @@ def exportSourceLayer(ds_src : gdal.Dataset,
lyr_src : ogr.Layer,
lyr_dst : ogr.Layer,
layerdef : dict[str,Any],
+ fieldMap : tuple[list[str],list[int]],
extent : ogr.Geometry|None = None) -> int:
"""Export a source layer."""
count0 = -1
@@ -168,7 +169,7 @@ def exportSourceLayer(ds_src : gdal.Dataset,
spatialFilter = getSpatialFilterFromGeometry(extent, srs_src)
transform_geometry = layerdef.get('transform-geometry', None)
- columns = [ 'm.' + escape_identifier(lyr_src.GetFIDColumn()) ]
+ columns = [ 'm.' + escape_identifier(lyr_src.GetFIDColumn()) ] + fieldMap[0]
geomFieldName_esc = escape_identifier(geomField.GetName())
if transform_geometry is None:
columns.append('m.' + geomFieldName_esc)
@@ -194,16 +195,26 @@ def exportSourceLayer(ds_src : gdal.Dataset,
logging.debug('Source layer "%s" has %d features, of which %d are to be exported',
layername, count0, count1)
+ fieldMap = fieldMap[1]
+ logging.debug('Field map: %s', str(fieldMap))
+
+ geom_type = lyr_src.GetGeomType()
+ bFlatten = geom_type == ogr.wkbUnknown or ogr.GT_HasM(geom_type) or ogr.GT_HasZ(geom_type)
+ bTransform = bFlatten or ct is not None
+
feature_count = 0
defn_dst = lyr_dst.GetLayerDefn()
feature = lyr_src.GetNextFeature()
while feature is not None:
- geom = feature.GetGeometryRef().Clone()
- if ct is not None and geom.Transform(ct) != ogr.OGRERR_NONE:
- raise RuntimeError('Could not apply coordinate transformation')
- geom.FlattenTo2D()
feature2 = ogr.Feature(defn_dst)
- feature2.SetGeometryDirectly(geom)
+ feature2.SetFromWithMap(feature, False, fieldMap)
+ if bTransform:
+ geom = feature2.GetGeometryRef()
+ if ct is not None and geom.Transform(ct) != ogr.OGRERR_NONE:
+ raise RuntimeError('Could not apply coordinate transformation')
+ if bFlatten:
+ geom.FlattenTo2D()
+ feature2.SetGeometryDirectly(geom)
feature2.SetFID(feature.GetFID())
if lyr_dst.CreateFeature(feature2) != ogr.OGRERR_NONE:
raise RuntimeError(f'Could not transfer source feature #{feature.GetFID()}')
@@ -351,7 +362,85 @@ def exportMetadata(basedir : Path, data : dict[str,Any],
finally:
os.close(fd)
-# pylint: disable-next=too-many-branches, too-many-statements
+def getFieldMap(lyr_dst : ogr.Layer, lyr_src : ogr.Layer,
+ drv_src : gdal.Driver,
+ fieldMap : dict[str,str]|None) -> tuple[list[str],list[int]]:
+ """Create fields on the destination MVT layer, and return a list of
+ column statements along with a field map for the MVT export."""
+ if fieldMap is None or len(fieldMap) == 0:
+ return [], []
+
+ if not lyr_dst.TestCapability(ogr.OLCCreateField):
+ raise RuntimeError(f'Destination layer "{lyr_dst.GetName()}" lacks '
+ 'field creation capability')
+
+ columns = {}
+ defn_src = lyr_src.GetLayerDefn()
+ for fld_dst, fld_src in fieldMap.items():
+ idx_src = defn_src.GetFieldIndex(fld_src)
+ if idx_src < 0:
+ raise RuntimeError(f'Source layer "{lyr_src.GetName()}" has no field named "{fld_src}"')
+
+ defn_dst = ogr.FieldDefn()
+ defn_src_fld = defn_src.GetFieldDefn(idx_src)
+ if fld_dst == 'age':
+ if defn_src_fld.GetType() not in (ogr.OFTDate, ogr.OFTDateTime):
+ raise RuntimeError(f'Field "{fld_src}" of source layer "{lyr_src.GetName()}"'
+ ' has type ' + ogr.GetFieldTypeName(defn_src_fld.GetType()) +
+ ' (Date or DateTime expected)')
+ defn_dst.SetType(ogr.OFTInteger)
+ # signed int16 allows expressing dates from 1880-04-15 to 2059-09-18
+ # which should be more than enough (it's not clear if the MVT format takes
+ # advantage of the reduced storage though)
+ defn_dst.SetSubType(ogr.OFSTInt16)
+
+ # TODO[GDAL >=3.9] use lyr_src.GetDataset().GetDriver()
+ if drv_src.ShortName == 'PostgreSQL':
+ column = 'CAST(m.' + escape_identifier(fld_src)
+ column += ' - date \'1970-01-01\' AS smallint)'
+ elif drv_src.ShortName in ('SQLite', 'GPKG'):
+ column = 'CAST(floor(julianday(m.' + escape_identifier(fld_src) + ')'
+ column += ' - 2440587.5) AS smallint)'
+ else:
+ raise NotImplementedError(f'Unsupported source driver {drv_src.ShortName} for '
+ f'field "{fld_src}" (MVT field "{fld_dst}")')
+
+ else:
+ raise NotImplementedError(f'Destination MVT field "{fld_dst}"')
+
+ columns[fld_dst] = column
+
+ defn_dst.SetName(fld_dst)
+ defn_dst.SetNullable(defn_src_fld.IsNullable())
+ logging.debug('Create output field "%s" with type=%s, subtype=%s, nullable=%d',
+ defn_dst.GetName(),
+ ogr.GetFieldTypeName(defn_dst.GetType()),
+ ogr.GetFieldSubTypeName(defn_dst.GetSubType()),
+ defn_dst.IsNullable())
+
+ if lyr_dst.CreateField(defn_dst, approx_ok=False) != gdal.CE_None:
+ raise RuntimeError(f'Could not create field "{fld_dst}" '
+ f'in destination MVT layer "{lyr_dst.GetName()}"')
+
+ indices = {}
+ defn_dst = lyr_dst.GetLayerDefn()
+ for i in range(defn_dst.GetFieldCount()):
+ fld = defn_dst.GetFieldDefn(i)
+ name = fld.GetName()
+ if name in columns:
+ indices[name] = i
+ else:
+ logging.warning('Destination layer has unknown field #%d "%s"', i, name)
+
+ ret = [None] * len(columns)
+ fieldMap = [-1] * defn_dst.GetFieldCount()
+ for idx, name in enumerate(columns.keys()):
+ i = indices[name] # intentionally crash if we didn't create that field
+ fieldMap[i] = idx
+ ret[idx] = columns[name] + ' AS ' + escape_identifier(name)
+ return (ret, fieldMap)
+
+# pylint: disable-next=too-many-branches, too-many-locals, too-many-statements
def exportMVT(ds : gdal.Dataset,
layers : dict[str,dict[str,Any]],
sources : dict[str,Any],
@@ -436,10 +525,15 @@ def exportMVT(ds : gdal.Dataset,
if lyr_dst is None:
raise RuntimeError(f'Could not create destination layer "{layername}"')
+ fieldMap = getFieldMap(lyr_dst, lyr_src, drv_src=ds.GetDriver(),
+ fieldMap=layerdef.get('fields', None))
+
# TODO: GDAL exports features to a temporary SQLite database even though the source
# is PostGIS hence is able to generate MVT with ST_AsMVT(). Letting PostGIS generate
# tiles is likely to speed up things.
- feature_count += exportSourceLayer(ds, lyr_src, lyr_dst, layerdef, extent=extent)
+ feature_count += exportSourceLayer(ds, lyr_src, lyr_dst, layerdef,
+ fieldMap=fieldMap,
+ extent=extent)
layer_count += 1
lyr_dst = None
lyr_src = None