diff --git a/Project.toml b/Project.toml index 3534bbe..3b8d5cb 100644 --- a/Project.toml +++ b/Project.toml @@ -10,16 +10,19 @@ GeoFormatTypes = "68eda718-8dee-11e9-39e7-89f7f65f511f" GeoInterface = "cf35fbd7-0cd7-5166-be24-54bfbe79505f" GeoInterfaceMakie = "0edc0954-3250-4c18-859d-ec71c1660c08" GeoInterfaceRecipes = "0329782f-3d07-4b52-b9f6-d3137cf03c7a" +Mmap = "a63ad114-7e13-5084-954f-fe012c677804" OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01" Tables = "bd369af6-aec1-5ad0-b16a-f7cc5008161c" [weakdeps] Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a" +ZipArchives = "49080126-0e18-4c2a-b176-c102e4b3760c" ZipFile = "a5390f91-8eb1-5f08-bee0-b1d1ffed6cea" [extensions] ShapefileMakieExt = "Makie" +ShapefileZipArchivesExt = "ZipArchives" ShapefileZipFileExt = "ZipFile" [compat] @@ -30,15 +33,17 @@ GeoInterface = "1.0" GeoInterfaceMakie = "0.1" GeoInterfaceRecipes = "1.0" Makie = "0.20, 0.21, 0.22" +Mmap = "1.11.0" OrderedCollections = "1" RecipesBase = "1" Tables = "0.2, 1" +ZipArchives = "2" ZipFile = "0.9, 0.10" julia = "1.9" [extras] -ArchGDAL = "c9ce4bd3-c3d5-55b8-8973-c0e20141b8c3" Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" +ArchGDAL = "c9ce4bd3-c3d5-55b8-8973-c0e20141b8c3" DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a" Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" diff --git a/ext/ShapefileZipArchivesExt.jl b/ext/ShapefileZipArchivesExt.jl new file mode 100644 index 0000000..1230ce5 --- /dev/null +++ b/ext/ShapefileZipArchivesExt.jl @@ -0,0 +1,51 @@ +module ShapefileZipArchivesExt +import ZipArchives, Shapefile +import Shapefile: _read_shp_from_ziparchive, _is_ziparchives_loaded +import Mmap # to present zip files as AbstractVector{UInt8} + +_is_ziparchives_loaded() = true + +function _read_shp_from_ziparchive(zipfile) + zip_file_handler = open(zipfile) + mmapped_zip_file = Mmap.mmap(zip_file_handler) + r = ZipArchives.ZipReader(mmapped_zip_file) + # need to get dbx + shpdata, shxdata, dbfdata, prjdata = nothing, nothing, nothing, nothing + for filename in ZipArchives.zip_names(r) + lfn = lowercase(filename) + if endswith(lfn, ".shp") + shpdata = IOBuffer(ZipArchives.zip_readentry(r, filename)) + elseif endswith(lfn, ".shx") + shxdata = ZipArchives.zip_readentry(r, filename, Shapefile.IndexHandle) + elseif endswith(lfn, ".dbf") + ZipArchives.zip_openentry(r, filename) do io + dbfdata = Shapefile.DBFTables.Table(io) + end + elseif endswith(lfn, "prj") + prjdata = try + Shapefile.GeoFormatTypes.ESRIWellKnownText(Shapefile.GeoFormatTypes.CRS(), ZipArchives.zip_readentry(r, filename, String)) + catch + @warn "Projection file $zipfile/$lfn appears to be corrupted. `nothing` used for `crs`" + nothing + end + end + end + + # Finished the reading loop, we don't need the zip file anymore. + + close(zip_file_handler) + + # Populate the Shapefile.Table + @assert shpdata !== nothing + shp = if shxdata !== nothing # we have shxdata/index + read(shpdata, Shapefile.Handle, shxdata) + else + read(shpdata, Shapefile.Handle) + end + if prjdata !== nothing + shp.crs = prjdata + end + return Shapefile.Table(shp, dbfdata) +end + +end \ No newline at end of file diff --git a/ext/ShapefileZipFileExt.jl b/ext/ShapefileZipFileExt.jl index 7a05053..96b0e10 100644 --- a/ext/ShapefileZipFileExt.jl +++ b/ext/ShapefileZipFileExt.jl @@ -1,6 +1,9 @@ module ShapefileZipFileExt import ZipFile, Shapefile -import Shapefile: _read_shp_from_zipfile +import Shapefile: _read_shp_from_zipfile, _is_zipfiles_loaded + +_is_zipfiles_loaded() = true + function _read_shp_from_zipfile(zipfile) r = ZipFile.Reader(zipfile) # need to get dbx diff --git a/src/table.jl b/src/table.jl index 05bbd64..7881930 100644 --- a/src/table.jl +++ b/src/table.jl @@ -68,7 +68,16 @@ function Table(shp::Handle{T}, dbf::DBFTables.Table) where {T} end function Table(path::AbstractString) if endswith(path, ".zip") - return _read_shp_from_zipfile(path) + if _is_ziparchives_loaded() + return _read_shp_from_ziparchive(path) + elseif _is_zipfiles_loaded() + return _read_shp_from_zipfile(path) + else + throw(ArgumentError(""" + ZipFile.jl or ZipArchives.jl are not loaded, so Shapefile.jl cannot read zip files. + Please run `using ZipArchives` or `using ZipFile` to enable reading zip files. + """)) + end end paths = _shape_paths(path) isfile(paths.shp) || throw(ArgumentError("File not found: $(paths.dbf)")) @@ -84,6 +93,10 @@ function Table(path::AbstractString) end function _read_shp_from_zipfile end +function _read_shp_from_ziparchive end + +_is_zipfiles_loaded(args...) = false # these are set to true in extensions +_is_ziparchives_loaded(args...) = false # these are set to true in extensions getshp(t::Table) = getfield(t, :shp) getdbf(t::Table) = getfield(t, :dbf)