diff options
| author | Guilhem Moulin <guilhem@fripost.org> | 2024-06-19 01:55:08 +0200 | 
|---|---|---|
| committer | Guilhem Moulin <guilhem@fripost.org> | 2024-06-19 03:32:29 +0200 | 
| commit | bc58a552b1be29d0f11d6856da60282034339e96 (patch) | |
| tree | 9b252bae0ec31cd75f9b542446466f4bec624729 | |
| parent | 35635ebf16aebfe3113806acca0de274679118fe (diff) | |
Add logic to replace field value literals.
And set them to NULL.
| -rw-r--r-- | config.yml | 80 | ||||
| -rw-r--r-- | schema.sql | 50 | ||||
| -rwxr-xr-x | webmap-import | 99 | 
3 files changed, 205 insertions, 24 deletions
| @@ -228,8 +228,21 @@ layers:  #            - field_name1  #            - field_name2  #          field-map: +#            source_field_name1: field_name1  #            source_field_name2: field_name2 -#            source_field_name2: field_name2 +# +#          # Mapping of (source) field values, to replace particular +#          # values with something else.  Useful for instance to replace +#          # the empty string with NULL. +#          value-map: +#            source_field_name1: +#              - from: '' +#                to: null +#              - from: 'foo' +#                to: 'bar' +#            source_field_name2: +#              - from: 0 +#                to: 1    'lansyta':      description: Sveriges län (Lantmäteriet) @@ -521,9 +534,9 @@ layers:          - name: Beteckn            type: String            width: 12 -          #unique: true -          # TODO: map "Visas ej" to NULL -          nullable: false +          unique: true +          # obfuscated codes are mapped to NULL +          nullable: true            #comment: ärendebeteckning          - name: ArendeAr            type: Integer @@ -611,6 +624,10 @@ layers:              Forebild: Forebild              Efterbild: Efterbild              Arealha: ArealHa +          value-map: +            Beteckn: +              - from: 'Visas ej' +                to: null        - source:            download: @@ -642,6 +659,10 @@ layers:              Forebild: Forebild              Efterbild: Efterbild              Arealha: ArealHa +          value-map: +            Beteckn: +              - from: 'Visas ej' +                to: null        - source:            download: @@ -673,6 +694,10 @@ layers:              Forebild: Forebild              Efterbild: Efterbild              Arealha: ArealHa +          value-map: +            Beteckn: +              - from: 'Visas ej' +                to: null        - source:            download: @@ -702,6 +727,10 @@ layers:              Forebild: Forebild              Efterbild: Efterbild              Arealha: ArealHa +          value-map: +            Beteckn: +              - from: 'Visas ej' +                to: null    'st:betesomraden':      source: @@ -807,8 +836,7 @@ layers:            #comment: när tillståndets giltighet slutar          - name: DiaryNr            type: String -          # TODO map empty string to NULL -          #unique: true +          unique: true            width: 16          - name: LastUpdated            # TODO convert to Date @@ -832,6 +860,10 @@ layers:          'Valid to': ValidTo          'Diary nr': DiaryNr          'Last updated': LastUpdated +      value-map: +        'Diary nr': +          - from: '' +            to: null    'mrr:markanvisningar':      description: Markanvisning till koncession (SGU) @@ -845,12 +877,10 @@ layers:            #comment: benämning på området          - name: DecisionDate            type: Date -          # TODO map empty string to NULL            #comment: beslutsdatum          - name: DiaryNr            type: String -          # TODO map empty string to NULL -          #unique: true +          unique: true            width: 16          - name: LastUpdated            # TODO convert to Date @@ -871,6 +901,13 @@ layers:          'Decision date': DecisionDate          'Diary nr': DiaryNr          'Last updated': LastUpdated +      value-map: +        'Decision date': +          - from: '' +            to: null +        'Diary nr': +          - from: '' +            to: null    'mrr:mineral_applied':      description: Undersökningstillstånd, metallar och mineral, ansökta (SGU) @@ -959,8 +996,7 @@ layers:            #comment: när tillståndets giltighet slutar          - name: DiaryNr            type: String -          # TODO map empty string to NULL -          #unique: true +          unique: true            width: 16          - name: LastUpdated            # TODO convert to Date @@ -985,6 +1021,10 @@ layers:          'Valid to': ValidTo          'Diary nr': DiaryNr          'Last updated': LastUpdated +      value-map: +        'Diary nr': +          - from: '' +            to: null  #  'mrr:mineral_expired':  #    source: @@ -1100,8 +1140,7 @@ layers:            #comment: när tillståndets giltighet slutar          - name: DiaryNr            type: String -          # TODO map empty string to NULL -          #unique: true +          unique: true            width: 16          - name: LastUpdated            # TODO convert to Date @@ -1126,6 +1165,10 @@ layers:          'Valid to': ValidTo          'Diary nr': DiaryNr          'Last updated': LastUpdated +      value-map: +        'Diary nr': +          - from: '' +            to: null    'mrr:torvkoncessioner':      description: Torvkoncessioner (SGU) @@ -1140,7 +1183,6 @@ layers:            #comment: benämning på området          - name: Mineral            type: String -          # TODO map empty string to NULL            width: 254            #comment: koncessionsmineral          - name: Owner @@ -1158,8 +1200,7 @@ layers:            #comment: när tillståndets giltighet slutar          - name: DiaryNr            type: String -          # TODO map empty string to NULL -          #unique: true +          unique: true            width: 16          - name: LastUpdated            # TODO convert to Date @@ -1183,3 +1224,10 @@ layers:          'Valid to': ValidTo          'Diary nr': DiaryNr          'Last updated': LastUpdated +      value-map: +        'Mineral': +          - from: '' +            to: null +        'Diary nr': +          - from: '' +            to: null @@ -591,7 +591,7 @@ ALTER SEQUENCE postgis."sks:AvverkAnm_ogc_fid_seq" OWNED BY postgis."sks:AvverkA  CREATE TABLE postgis."sks:UtfordAvverk" (      ogc_fid bigint NOT NULL,      "OBJECTID" integer NOT NULL, -    "Beteckn" character(12) NOT NULL, +    "Beteckn" character(12),      "ArendeAr" smallint NOT NULL,      "Avverktyp" character varying(254) NOT NULL,      "Skogstyp" character varying(254) NOT NULL, @@ -795,6 +795,14 @@ ALTER TABLE ONLY postgis."mrr:bearbetningskoncessioner_applied"  -- +-- Name: mrr:bearbetningskoncessioner_approved mrr:bearbetningskoncessioner_approved_DiaryNr_key; Type: CONSTRAINT; Schema: postgis; Owner: webmap_import +-- + +ALTER TABLE ONLY postgis."mrr:bearbetningskoncessioner_approved" +    ADD CONSTRAINT "mrr:bearbetningskoncessioner_approved_DiaryNr_key" UNIQUE ("DiaryNr"); + + +--  -- Name: mrr:bearbetningskoncessioner_approved mrr:bearbetningskoncessioner_approved_Name_key; Type: CONSTRAINT; Schema: postgis; Owner: webmap_import  -- @@ -811,6 +819,14 @@ ALTER TABLE ONLY postgis."mrr:bearbetningskoncessioner_approved"  -- +-- Name: mrr:markanvisningar mrr:markanvisningar_DiaryNr_key; Type: CONSTRAINT; Schema: postgis; Owner: webmap_import +-- + +ALTER TABLE ONLY postgis."mrr:markanvisningar" +    ADD CONSTRAINT "mrr:markanvisningar_DiaryNr_key" UNIQUE ("DiaryNr"); + + +--  -- Name: mrr:markanvisningar mrr:markanvisningar_pkey; Type: CONSTRAINT; Schema: postgis; Owner: webmap_import  -- @@ -843,6 +859,14 @@ ALTER TABLE ONLY postgis."mrr:mineral_applied"  -- +-- Name: mrr:mineral_approved mrr:mineral_approved_DiaryNr_key; Type: CONSTRAINT; Schema: postgis; Owner: webmap_import +-- + +ALTER TABLE ONLY postgis."mrr:mineral_approved" +    ADD CONSTRAINT "mrr:mineral_approved_DiaryNr_key" UNIQUE ("DiaryNr"); + + +--  -- Name: mrr:mineral_approved mrr:mineral_approved_LicenceID_key; Type: CONSTRAINT; Schema: postgis; Owner: webmap_import  -- @@ -891,6 +915,14 @@ ALTER TABLE ONLY postgis."mrr:olja_gas_diamant_applied"  -- +-- Name: mrr:olja_gas_diamant_approved mrr:olja_gas_diamant_approved_DiaryNr_key; Type: CONSTRAINT; Schema: postgis; Owner: webmap_import +-- + +ALTER TABLE ONLY postgis."mrr:olja_gas_diamant_approved" +    ADD CONSTRAINT "mrr:olja_gas_diamant_approved_DiaryNr_key" UNIQUE ("DiaryNr"); + + +--  -- Name: mrr:olja_gas_diamant_approved mrr:olja_gas_diamant_approved_LicenceID_key; Type: CONSTRAINT; Schema: postgis; Owner: webmap_import  -- @@ -915,6 +947,14 @@ ALTER TABLE ONLY postgis."mrr:olja_gas_diamant_approved"  -- +-- Name: mrr:torvkoncessioner mrr:torvkoncessioner_DiaryNr_key; Type: CONSTRAINT; Schema: postgis; Owner: webmap_import +-- + +ALTER TABLE ONLY postgis."mrr:torvkoncessioner" +    ADD CONSTRAINT "mrr:torvkoncessioner_DiaryNr_key" UNIQUE ("DiaryNr"); + + +--  -- Name: mrr:torvkoncessioner mrr:torvkoncessioner_Name_key; Type: CONSTRAINT; Schema: postgis; Owner: webmap_import  -- @@ -955,6 +995,14 @@ ALTER TABLE ONLY postgis."sks:AvverkAnm"  -- +-- Name: sks:UtfordAvverk sks:UtfordAvverk_Beteckn_key; Type: CONSTRAINT; Schema: postgis; Owner: webmap_import +-- + +ALTER TABLE ONLY postgis."sks:UtfordAvverk" +    ADD CONSTRAINT "sks:UtfordAvverk_Beteckn_key" UNIQUE ("Beteckn"); + + +--  -- Name: sks:UtfordAvverk sks:UtfordAvverk_OBJECTID_key; Type: CONSTRAINT; Schema: postgis; Owner: webmap_import  -- diff --git a/webmap-import b/webmap-import index e9ae302..e978f34 100755 --- a/webmap-import +++ b/webmap-import @@ -730,8 +730,9 @@ def setOutputFieldMap(defn, sources):          fieldMap[fldName] = i      for source in sources: -        src = source['source']['path'] -        fieldMap2 = source['import'].get('field-map', None) +        source_import = source['import'] + +        fieldMap2 = source_import.get('field-map', None)          if fieldMap2 is None:              fieldMap2 = fieldMap          else: @@ -744,10 +745,22 @@ def setOutputFieldMap(defn, sources):                  if i is None:                      raise Exception(f'Ouput layer has no field named "{ofld}"')                  fieldMap2[ifld] = i - -        source['import']['field-map'] = fieldMap2 - -    return fieldMap +        source_import['field-map'] = fieldMap2 + +        # validate field value mapping +        valueMap = source_import.get('value-map', None) +        if valueMap is not None: +            for fldName, rules in valueMap.items(): +                if rules is None: +                    continue +                if not isinstance(rules, list): +                    rules = [rules] +                for idx, rule in enumerate(rules): +                    if rule is None or not isinstance(rule, dict): +                        raise Exception(f'Field "{fldName}" has invalid rule #{idx}: {rule}') +                    if 'from' not in rule or 'to' not in rule or len(rule) != 2: +                        raise Exception(f'Field "{fldName}" has invalid rule #{idx}: {rule}') +                    rules[idx] = ( rule['from'], rule['to'] )  # Escape the given identifier, cf.  # swig/python/gdal-utils/osgeo_utils/samples/validate_gpkg.py:_esc_id() @@ -856,6 +869,31 @@ def importSource(lyr, path=None, unar=None, args={}, cachedir=None, extent=None)          return importSource2(lyr, ds_srcpath, args=args,                      basedir=Path(tmpdir), extent=extent) +# Validate field value mapping +def setFieldMapValue(fld, idx, val): +    if val is None: +        if not fld.IsNullable(): +            logging.warning('Field "%s" is not NULLable but remaps NULL', fld.GetName()) +        return None + +    fldType = fld.GetType() +    if fldType == ogr.OFTInteger or fldType == ogr.OFTInteger64: +        if isinstance(val, int): +            return val +    elif fldType == ogr.OFTString: +        if isinstance(val, str): +            return val +    elif fldType == ogr.OFTBinary: +        if isinstance(val, bytes): +            return val +    elif fldType == ogr.OFTReal: +        if isinstance(val, int): +            return float(val) +        elif isinstance(val, float): +            return val + +    raise Exception(f'Field "{fld.GetName()}" mapping #{idx} has incompatible type for {ogr.GetFieldTypeName(fldType)}') +  # Import a source layer (already extracted)  # This is more or less like ogr2ogr/GDALVectorTranslate() but we roll  # out our own (slower) version because GDALVectorTranslate() insists in @@ -927,7 +965,6 @@ def importSource2(lyr_dst, path, args={}, basedir=None, extent=None):              # call SetIgnored() on unwanted source fields              logging.debug('Set Ignored=True on output field "%s"', fldName)              fld.SetIgnored(True) -    defn = None      count0 = -1      if lyr.TestCapability(ogr.OLCFastFeatureCount): @@ -965,6 +1002,32 @@ def importSource2(lyr_dst, path, args={}, basedir=None, extent=None):          else:              logging.info('Source layer "%s" has %d features', layername, count0) +    # build a list of pairs (field index, mapping_dict) +    valueMapLiteral = [] +    for fldName, rules in args.get('value-map', {}).items(): +        i = defn.GetFieldIndex(fldName) +        if i < 0: +            raise Exception(f'Source layer "{layername}" has no field named "{fldName}"') +        if fieldMap[i] < 0: +            logging.warning('Ignored source field "%s" has value map', fldName) +            continue + +        h = {} +        fld = defn.GetFieldDefn(i) +        for idx, (rFrom, rTo) in enumerate(rules): +            # use fld for both from and to (the types must match, +            # casting is not allowed in the mapping) +            rFrom = setFieldMapValue(fld, idx, rFrom) +            rTo   = setFieldMapValue(fld, idx, rTo) +            h[rFrom] = rTo + +        if len(h) > 0: +            valueMapLiteral.append((i, h)) + +    bValueMapLiteral = len(valueMapLiteral) > 0 + +    defn = None +      defn_dst = lyr_dst.GetLayerDefn()      eGType_dst = defn_dst.GetGeomType()      eGType_dst_HasZ = ogr.GT_HasZ(eGType_dst) @@ -975,6 +1038,28 @@ def importSource2(lyr_dst, path, args={}, basedir=None, extent=None):      mismatch = {}      feature = lyr.GetNextFeature()      while feature is not None: +        if bValueMapLiteral: +            for i, h in valueMapLiteral: +                if not feature.IsFieldSet(i): +                    continue +                elif feature.IsFieldNull(i): +                    if None in h: +                        v = h[None] +                        if v is not None: +                            # replace NULL with non-NULL value +                            feature.SetField(i, v) +                    continue + +                v = feature.GetField(i) +                if v in h: +                    v2 = h[v] +                    if v2 is None: +                        # replace non-NULL value with NULL +                        feature.SetFieldNull(i) +                    else: +                        # replace non-NULL value with non-NULL value +                        feature.SetField(i, v2) +          feature2 = ogr.Feature(defn_dst)          feature2.SetFromWithMap(feature, False, fieldMap) | 
