Skip to content

Conversation

@jph6366
Copy link

@jph6366 jph6366 commented Aug 4, 2025

Quick Summary

I am looking to add my contributions to help get this GeoIO feature merged in with compliance to the GPkg implementation and to be interoperable with GDALs reader and writer for GPkg also be able to seamlessly connect with Desktop QGIS & ArcGIS. I logged maybe ~25-30 hours into my contribution but it does need to be organized and divided into concise files, as well as more cleanup for optimizing to read more like native Julia code. There is also featured code that is outside of the scope of this feature request, such as "attributes" tables, and gpkg_extensions

Long-winded introduction

I am interetested in picking up Julia as a programming language so I began studying and reading the Julia documentation in June (2025) and practicing leetcode and advent of code problems to learn its quirks. I am also applying for my MS in Data Science, so I've become invested in the Geospatial Data Science with Julia (since I come from a professional geospatial software engineering background).

I also read up on SQLite's documentation, Julia Databases, and of course some of the JuliaEarth & JuliaGeo repositories and their source code. I wish I had discovered this community and Julia sooner!

With this, my ground work of rote memorization is done, so I can consciously answer questions about GPkg and relevant toolings.

You can view my monolith ORG file of all my notes here

https://jph6366.github.io/filing-cabinet/TOP-DOWN/Kanban/GeoPackage.html

Implementation Plan

  • load implementation consists of SQLite.jl simple SELECT statements that convert

    1. the table of attributes stored in the database into a Tables.jl table
    2. the geometries and spatial_ref_sys into a vector of geometries geoms from Meshes.jl
      results in a /geotable = georef(table, geoms)/ call to combine these two objects into a GeoTable
  • the save implementation consists of SQLite CREATE TABLE statements

    1. the /values(geotable)/ as a SQLite attribute table
    2. the /domain(geotable)/ as SQLite geometries, enabling R-Tree for non-trivial amount of vector data

Remarks

  • Important for GeoPackages to contain the following extensions
    • R-tree Spatial Indexes for saving non-trival amount of vector data
    • WKT for Coordinate Reference Systems due to weaknesses in the original standard for encoding coordinate reference systems

Walkthrough of Implementation

I don't use any sort of CoPilot or AI inside my editor, but I will use a LLM as a pair programmer or a domain-specific search engine. Often to generate code to explore alternative options and that is not usable code usually. This means there is no 'AI-generated' code in this PR.

  • After my research phase, I began completing items on the warm-up list until I was comfortable reading in Gpkg files, doing some basic validation.
  • I went straight to the actual GDAL repository and began translating the cpp implementation of the GeoPackage into Julia

https://github.com/OSGeo/gdal/tree/master/ogr/ogrsf_frmts/gpkg

  • reading the geopackage binary header

  • modeling my wkbGeometryTypes after GDAL

  • writing geometry from WKB functions

  • Optimized code to use Julia conventions such as IOBuffer and hoisting and making algorithmic changes

  • For writing the GPkg followed the sequence of operations from GDAL's gpkg Create function

  • also followed conventions mentioned in SQLite.jl to write concise code loading in Tables.jl compatible formats

  • basically just rewrote the original from_wkb (from reader) functions into to_wkb functions

Warmup
  1. parse bytes and SQLite database header

    • first 16 bytes are 'SQLite format 3'
    • "GPKG" in ASCII in /application_id/ and /user_version/
  2. execute PRAGMA integrity_check and foreign_key_check

  3. read gpkg_spatial_ref_sys

    • shall contain at minimum

      • /srs_name/: any
      • /srs_id/: 4326 | -1 | 0
      • /organization/: 4326 | -1 | 0
      • /organization_coordsys_id/: 4326 | -1 | 0
      • /definition/: any | undefined | undefined
      • /description/: any
    • extension for WKT for Coordinate Reference Systems

      • parse WKT encoded simple features geometry from column /definition_12_063/
        • takes priority over definition column
  4. read gpkg_contents

    • values in /srs_id/ column reference values in gpkg_spatial_ref_sys table /srs_id/ column
    • /table_name/ column value contains the name of a SQLite table
    • /data_type/ specifies type of content in table referenced in /table_name/
      • "features" vector features
  5. read gpkg_geometry_column

    • /srs_id/ value is an /srs_id/ column from gpkg_spatial_ref_sys table
    • values of /table_name/ column references values in gpkg_contents /table_name/
      • for rows wth a "features" /data_type/
    • the /z/ and /m/ value is one of 0 , 1, or 2
      • meaning either z/m values prohibited, mandatory, or optional respectively
    • /column_name/ column value in is one of the uppercase core geometry types or *extensions
      • GEOMETRY
        subtypes are

        • POINT
        • *CURVE
          subtypes are
          • *LINESTRING
          • *CIRCULARSTRING
          • *COMPOUNDCURVE
        • SURFACE
          subtype is
          • *CURVEPOLYGON
            subtype is
            • POLYGON
        • GEOMETRYCOLLECTION
          subtypes are
          • MULTIPOINT
          • *MULTICURVE
            subtype is
            • MULTILINESTRING
          • *MULTISURFACE
            subtype is
            • MULTIPOLYGON

              /Non-linear Geometry types not really commonly supported/

      • given /column_name/ and /table_name/ declared SQL type of the feature table geometry is specified by /geometry_type_name/ column also specifying the /srs_id/ of feature table

      • feature table geometries are stored in geopackagebinary format

      • read blob to infer header and geometry

        • handle Standard, Extended, ISO WKB Geometry
          • test for empty gpkgbinary and axis order
  6. read gpkg_extensions table

    • /table_name/ column references values in gpkg_contents /table_name/ column
      • these reference new tables required by that extension or be NULL
    • /column_name/ column value the name of the column in the table specified by the /table_name/ column value for that row, or be NULL
    • /extension_name/ column value is a unique case sensitve value
      • forms _<extension_name>
    • /scope/ column value indicates whether extension affects reads and/or writes

Happy Testing

  1. Reads in the *.gpkg into GeoTables smoothly
    1. Reads GeoPackageBinary
      1. Standard, ISO, Extended WKB
  2. Save recreates the OGC GeoPackage Spec Requirements
    1. Core Requirements
    2. Features Requirements
    3. Attributes Requirements
    4. Extensions Requirements
      a. R-Tree Spatial Indexes (write)
      b. WKT for SRS (write)
  3. can load/save all '*.gpkg' files from GADM and GeoBR
simple features
- points.gpkg
- lines.gpkg
- polygon.gpkg
- multi.gpkg
non-spatial
- attributes.gpkg
    /includes attributes, vector features, and feature attributes/
  - rivers.gpkg
Z-dimension Coordinate{{x,y},z}
  • gdal_sample.gpkg
non-trivial amount of vector features
  • rivers.gpkg

UnHappy Testing

  • empty geopackage
    • IEEE-754 quiet NaN value Point Geometry
  • gdal_sample.gpkg
    • wkbUnknown
    • wkbGeometryCollection
    • CoordRefSystems Conversion Error
      • UTM Zone <--> Cartesian
  • does it work in QGIS?

E2E Test

  1. read in *.gpkg file into a GeoTable
  2. write that GeoTable to a *.gpkg file
  3. read the new *.gpkg file into GeoTable
    a. check if the GeoTables are equivalent
  4. write new GeoTable to a *.gpkg file
  5. compare differences from original GeoPackage file and current GeoPackage file
  6. Load outputted gpkg file(s) into Desktop ArcGIS and Desktop QGIS

Reading

@jph6366 jph6366 changed the title initial commit Geopackage reader/writer Aug 4, 2025
@jph6366 jph6366 changed the title Geopackage reader/writer WIP: Geopackage reader/writer Aug 4, 2025
@MendeBadra
Copy link

Hmm. Interesting. Is it finished? Is there are anything that I could help with?

@jph6366
Copy link
Author

jph6366 commented Aug 18, 2025

Hmm. Interesting. Is it finished? Is there are anything that I could help with?

No this will be closed most likely, but I may rescope this for a WKB Geometry implementation to better handle the well-known geometry (probably will close this still and open a new PR to avoid confusion.)

I also may spin off my work on this to contribute to the JuliaGeo/GeoPackage.jl there.

@jph6366
Copy link
Author

jph6366 commented Aug 18, 2025

Hmm. Interesting. Is it finished? Is there are anything that I could help with?

It does work for test cases except for e2e test and haven't implemented checks for empty geopackage,
see testing portion at the bottom

But there is a something missing that is causing invalid geopackage files in the writer

@juliohm
Copy link
Member

juliohm commented Aug 30, 2025

@jph6366 thanks for working on this. Your PR evolved a lot since the last time I saw it. I'd be happy to provide additional feedback if you plan to keep working on it.

The first thing I would suggest here is to leave the ArchGDAL.jl code intact for now. We can work on the GPKG backend without removing the GDAL fallback. After the PR is merged, I can take care of deprecating the fallback and removing it from the codebase.

@jph6366
Copy link
Author

jph6366 commented Aug 30, 2025

@jph6366 thanks for working on this. Your PR evolved a lot since the last time I saw it. I'd be happy to provide additional feedback if you plan to keep working on it.

@juliohm I would greatly appreciate feedback. I'm two weeks into my Data Science program and I have been practicing and reading and learning a lot more about Julia. Your feedback would be immensely valuable!

The first thing I would suggest here is to leave the ArchGDAL.jl code intact for now. We can work on the GPKG backend without removing the GDAL fallback. After the PR is merged, I can take care of deprecating the fallback and removing it from the codebase.

Yes I should have left this out of the PR. I actually couldn't get my ArchGDAL to precompile on my Ubuntu Desktop, but I have a fresh new university laptop that precompiled all the dependencies yesterday without trouble (on Windows).

I actually was planning on doing a rewrite this weekend (and I have a holiday Monday as well), so you're reply is well-timed!

@juliohm
Copy link
Member

juliohm commented Aug 30, 2025

That is really good to hear, @jph6366! 🙂 I will share a first review tonight (BRT time zone) or tomorrow.

Copy link
Member

@juliohm juliohm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jph6366 attached you can find a first round of review. There are many improvements to make before we can continue reviewing the code. Below are some general comments:

  1. Please remove the geopackage.org file from the PR
  2. Please undo the changes related to ArchGDAL.jl
  3. Try to avoid underscores in variable names
  4. Try to avoid type annotations

Will comb through for various edges cases (empty, 3D, etc.) once the writer is gpkg spec-compliant implementation
# Licensed under the MIT License. See LICENSE in the project root.
# ------------------------------------------------------------------

const DBInterface = SQLite.DBInterface
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if this should be in gpkg.jl or in GeoIO.jl (under import SQLite)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add DBInterface.jl to Project.toml with an appropriate compat entry. We can then add an explicit import in GeoIO.jl

@jph6366
Copy link
Author

jph6366 commented Sep 9, 2025

It does work for test cases except for e2e test and haven't implemented checks for empty geopackage, see testing portion at the bottom

But there is a something missing that is causing invalid geopackage files in the writer

Update 09/09/2025:

Next commit (in maybe a few days) will contain some code for struct GeoPackageGeometry and
function SQLite.sqlitetype_(::Type{GeoPackageGeometry}) to idiomatically define GeoPackage SQL geometry types. The different geometry type methods will be defined by overloading methods 'meshfromwkb' & 'writewkbgeom' and dispatched in a similar fashion to CoordRefSystems crs codes. Planning to progress towards optimized julia code for type stable function calls and also avoiding unnecessary allocations, also to divide and conquer WKB logic to its own file (wkb.jl). Ideally this implementation can be reused to support future WKB Geometry integrations into GeoStats.jl and offer an alternative to WellKnownGeometry.jl's implementation of GeoInterface.

The status for the gpkg writer is still not compliant (enough) to pass the E2E test. It writes valid Standard GeoPackage Binary Header, but it does not handle the WKB Geometry encoding correctly. Still no cases for empty GeoPackage contents and need to test on Geometries with Z values

Currently testing using a GADM gpkg file for the US Virgin Islands (a geopackage with 3 tables all with MultiPolygon types) until the writer begins to create valid, readable geopackage files that seamlessly work with other GIS softwares (w/ gpkg support). So I imagine the writer also may be incorrect for other more simple features.

Once the status of the GPKG I/O implementation is complete, then I will consider adding functionality for gpkg_extensions table, R-Tree spatial indexes (write-only), and then (aspatial) attributes. There is also the consideration for non-linear geometry type support and/or creation of custom WKB geometry types/format

@juliohm
Copy link
Member

juliohm commented Sep 10, 2025

Thank you for the update @jph6366 ! I am traveling with limited internet connection, but will try to share more review comments when I find WiFi.

Copy link
Member

@juliohm juliohm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few more suggestions.

# query SQLite.Table given *.gpkg filename; optionally specify quantity of tables and features query
# default behavior for select statement is limit result to 1 feature geometry row and corresponding attributes row
# limited to only 1 feature table to select geopackage binary geometry from.
function gpkgread(fname; ntables=1, nfeatures=1)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Try to follow the convention adopted with the layer option. We don't want to support loading multiple tables in a single call. This is bad design of these file formats and specs.

Comment on lines +128 to +131
geomindex = findfirst(==(Symbol(geomcolumn)), keys(row))
values = map(keys(row)[[begin:(geomindex - 1); (geomindex + 1):end]]) do key
key, getproperty(row, key)
end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this code be simplified in terms of the named tuple snippet I shared?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as long we test against duplicate fieldnames then I don't think we can simplfy this to check for the Symbol(geomcolumn)

Comment on lines 181 to 184
headerlen = iszero(envelope) ? 8 : 8 + 8 * 2 * (envelope + 1)

# Skip reading the double[] envelope and start reading Well-Known Binary geometry
seek(buff, headerlen)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So you called read a couple of times to consume the IOBuffer. Aren't you counting the same bytes twice when you add them up in headerlen and then call seek?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I am. I'm swapping to using skip and starting from byte[4] given headerlen

Comment on lines 73 to 78
if _haszextent(wkbtypebits)
wkbtype = iszero(wkbtypebits & 0x80000000) ? wkbtypebits - 1000 : wkbtypebits & 0x7FFFFFFF
wkb2single(buff, crs, wkbtype, true, wkbbswap)
else
wkb2single(buff, crs, wkbtypebits, false, wkbbswap)
end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please check if the spec allows mixing geometries with and without z coordinates on the same multi-geometry? We don't want to support this.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can remove this I don't think this would happen, this is me overfitting the problem because its technically possible

Comment on lines 90 to 94
AND object_type IS NOT NULL
AND g.srs_id = srs.srs_id
AND g.srs_id = c.srs_id
AND g.z IN (0, 1, 2)
AND g.m = 0
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please clarify why we need to add these conditions here when the result is only used to retrieve metadata like srsid, org, tablename, etc.?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see Geometry Columns Table Requirements comments above or read reqs at https://www.geopackage.org/spec/#:~:text=2.1.5.1.2.%20Table%20Data%20Values

      # Requirement 26: The srs_id value in a gpkg_geometry_columns table row
      # SHALL be an srs_id column value from the gpkg_spatial_ref_sys table.
      #
      # Requirement 27: The z value in a gpkg_geometry_columns table row SHALL be one
      # of 0, 1, or 2.
      #
      # Requirement 28: The m value in a gpkg_geometry_columns table row SHALL be one
      # of 0, 1, or 2.
      #
      # Requirement 146: The srs_id value in a gpkg_geometry_columns table row
      # SHALL match the srs_id column value from the corresponding row in the
      # gpkg_contents table.
      
      # According to https://www.geopackage.org/spec/#r16 
      # Values of the gpkg_contents table srs_id column 
      # SHALL reference values in the gpkg_spatial_ref_sys table srs_id column
      # According to https://www.geopackage.org/spec/#r18
      # The gpkg_contents table SHALL contain a row 
      # with a lowercase data_type column value of "features" 
      # for each vector features user data table or view.

I'll remove what I think we can get away with

# gpkg_contents table.
function gpkgextract(db; layer=1)
# get the first (and only) feature table returned in sqlite query results
metadata = first(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the result only has a single entry, then it is appropriate to use only instead of first.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought so too initially but only returns

SQLite.Row{false}:
 :tablename      missing
 :geomcolumn     missing
 :srsid          missing
 :org            missing
 :orgcoordsysid  missing

each row is only valid when called and for some reason only doesn't do it?

Comment on lines 8 to 19
# supports wkbGeometryType according to
# SFSQL 1.1 high-bit flag 0x80000000 (Z)
# SFSQL 1.2 offset of 1000 (Z)
sfsql11 = iszero(typebits & 0x80000000)
sfsql12 = typebits > 1000
if !sfsql11 || sfsql12
wkbtype = sfsql11 ? typebits - 1000 : typebits & 0x7FFFFFFF
zextent = true
else
wkbtype = typebits
zextent = false
end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I refactored this code in an attempt to understand what is going on. The logic is a bit confusing, could you please explain in simple words?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sfsql11 checks if there is no high-bit-flag in the wkbtype bits, so the negating that condition returns true if its a wkbtype is a extended WKB type

sfsql12 checks the wkbtype bits is a number greater than the standard range of wkbtype (1-7), specifically ISO WKB type which is (1001-1007)

we want to set the wkbtypeZ to a standard WKB wkbtype and store zextent to indicate the Z-dimension for return the correction dimensions in wkb2point

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We want to accept EWKB or ISO flavoured WKB basically, but we prefer ISO for maximum compatiability.

Comment on lines 42 to 43
y = byteswap(read(buff, Float64))
x = byteswap(read(buff, Float64))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please double check this order? Are you aware that LatLon is "(y, x)" and the other CRS are "(x, y)"? Appreciate it if you could read the other opened PR with care to make sure that you are not introducing trivial bugs.

x = byteswap(read(buff, Float64))
if zextent
z = byteswap(read(buff, Float64))
return x, y, z
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is certainly not correct. This function should only return valid Point objects. I really recommend reading the other PR where we already sorted out a clean function to read buffers into points.

Comment on lines +90 to +91
if srsid == 0 || srsid == 4326
crs = isone(metadata.zextent) ? LatLonAlt{WGS84Latest} : LatLon{WGS84Latest}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where is this in the spec? I couldn't find from the link in the comment.

Comment on lines +115 to +118
geomindex = findfirst(==(Symbol(geomcolumn)), keys(row))
values = map(keys(row)[[begin:(geomindex - 1); (geomindex + 1):end]]) do key
key, getproperty(row, key)
end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I asked a few times without response: can we use a simpler namedtuple syntax here? Shared an example in a previous comment...

Comment on lines +168 to +171
skiplen = iszero(envelope) ? 4 : 4 + 8 * 2 * (envelope + 1)

# Skip reading the double[] envelope and start reading Well-Known Binary geometry
skip(buff, skiplen)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Asked a question here that was not addressed again...

if iszero(srs_id)
crs = LatLon{WGS84Latest}
elseif isone(abs(srs_id))
crs = zextent ? Cartesian{NoDatum,3} : Cartesian{NoDatum,2}
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(W/ BRANCH) 1.094199 seconds (628.18 k allocations: 31.932 MiB, 5.21% gc time, 99.20% compilation time)

(MERGED BRANCH) 1.068783 seconds (627.90 k allocations: 31.946 MiB, 5.72% gc time, 99.35% compilation time)

Tested on gpkg file with EPSG{27700}

Not sure if these metrics are entirely accurate (being ran in a jupyter nb) I will attempt set up a proper benchmark once I have the wkb.jl completed.


# Requirement 6: PRAGMA integrity_check returns a single row with the value 'ok'
# Requirement 7: PRAGMA foreign_key_check (w/ no parameter value) returns an empty result set
if (DBInterface.execute(db, "PRAGMA integrity_check;") |> first |> only != "ok") ||
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the returned result row iterator only supports a single-pass so we can't retrieve without using iterate(x) here.

first returns the

SQLite.Row{false}:
 :integrity_check  "ok"

only gets the only element in the collection which is "ok"

# gpkg_contents table.
function gpkgextract(db; layer=1)
# get the first (and only) feature table returned in sqlite query results
metadata = first(
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought so too initially but only returns

SQLite.Row{false}:
 :tablename      missing
 :geomcolumn     missing
 :srsid          missing
 :org            missing
 :orgcoordsysid  missing

each row is only valid when called and for some reason only doesn't do it?

Comment on lines 8 to 19
# supports wkbGeometryType according to
# SFSQL 1.1 high-bit flag 0x80000000 (Z)
# SFSQL 1.2 offset of 1000 (Z)
sfsql11 = iszero(typebits & 0x80000000)
sfsql12 = typebits > 1000
if !sfsql11 || sfsql12
wkbtype = sfsql11 ? typebits - 1000 : typebits & 0x7FFFFFFF
zextent = true
else
wkbtype = typebits
zextent = false
end
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sfsql11 checks if there is no high-bit-flag in the wkbtype bits, so the negating that condition returns true if its a wkbtype is a extended WKB type

sfsql12 checks the wkbtype bits is a number greater than the standard range of wkbtype (1-7), specifically ISO WKB type which is (1001-1007)

we want to set the wkbtypeZ to a standard WKB wkbtype and store zextent to indicate the Z-dimension for return the correction dimensions in wkb2point

Comment on lines 8 to 19
# supports wkbGeometryType according to
# SFSQL 1.1 high-bit flag 0x80000000 (Z)
# SFSQL 1.2 offset of 1000 (Z)
sfsql11 = iszero(typebits & 0x80000000)
sfsql12 = typebits > 1000
if !sfsql11 || sfsql12
wkbtype = sfsql11 ? typebits - 1000 : typebits & 0x7FFFFFFF
zextent = true
else
wkbtype = typebits
zextent = false
end
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We want to accept EWKB or ISO flavoured WKB basically, but we prefer ISO for maximum compatiability.

Comment on lines +128 to +131
geomindex = findfirst(==(Symbol(geomcolumn)), keys(row))
values = map(keys(row)[[begin:(geomindex - 1); (geomindex + 1):end]]) do key
key, getproperty(row, key)
end
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as long we test against duplicate fieldnames then I don't think we can simplfy this to check for the Symbol(geomcolumn)

Comment on lines 181 to 184
headerlen = iszero(envelope) ? 8 : 8 + 8 * 2 * (envelope + 1)

# Skip reading the double[] envelope and start reading Well-Known Binary geometry
seek(buff, headerlen)
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I am. I'm swapping to using skip and starting from byte[4] given headerlen

@jph6366 jph6366 closed this Nov 18, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants