This Odin library is written to support export and import from ESRI Shape files. Only two-dimensional features are supported in this version.
Map projections is supported, but limmited to EPSG-codes.
See also the Shapefile C Library
- v1.1 (Nov, 2025): Added support for .prj files.
import "shp"
Or the path to the library directory.
ShpHandle :: struct {
...
}
Keeps track of the files and read/write state.
ShpType :: enum {
null = 0,
point = 1,
line = 3, // same as polyline
area = 5, // same as polygon
multiPoint = 8,
// not supported:
pointZ = 11,
lineZ = 13,
areaZ = 15,
multiPointZ = 18,
pointM = 21,
lineM = 23,
areaM = 25,
multiPointM = 28,
multiPatch = 31
}
To get the shape type from a shape handle:
get_shape_type :: proc( handle :^ShpHandle) -> ShpType
Holds a single object or feature:
ShpObject :: struct #packed {
shpType : i32le,
box : [4]f64le,
numParts : i32le,
numPoints : i32le,
parts : [dynamic]i32le,
coords : [dynamic]ShpPoint, // points
attrs : [dynamic]DbfValue
}
Used to return a state when you request an attribute value from an object
ShpValState :: enum {
ok,
null, // the attribute is nill
incompatible // e.g. if you request an int from an attribute that is a string
}
Database field types
dbfStringAttribute :: u8('C')
dbfNumberAttribute :: u8('N')
dbfDateAttribute :: u8('D')
dbfFloatAttribute :: u8('F')
Open for reading:
open_files :: proc( filePath : string) -> (^ShpHandle, os.Error)
read_first_obj :: proc( handle :^ShpHandle) -> (^ShpObject, os.Error)
read_obj :: proc( handle :^ShpHandle, index : int) -> (^ShpObject, os.Error)
read_next_obj :: proc( handle :^ShpHandle) -> (^ShpObject, os.Error)
get_point_xy :: proc ( obj :^ShpObject) -> (f64, f64)
Asserts that obj is of type ShpType.point
get_parts ::proc( obj :^ShpObject) -> [dynamic][dynamic][2]f64
delete_parts :: proc (parts :[dynamic][dynamic][2]f64)
Asserts that obj is NOT of type ShpType.point
get_field_count :: proc( handle :ShpHandle) -> int
get_field_name :: proc( handle :ShpHandle, index :int) ->string
get_field_type :: proc( handle :ShpHandle, index :int) ->u8
get_field_size :: proc( handle :ShpHandle, index :int) ->(u8, u8)
// returns width, decimals
get_field_definition :: proc( handle :ShpHandle, index :int) ->(string, u8, u8, u8)
// returns name, type, width, decimals
get_string_value :: proc( obj :^ShpObject, attrIndex : int) -> (string, ShpValState)
get_int_value :: proc( obj :^ShpObject, attrIndex : int) -> (int, ShpValState)
get_float_value :: proc( obj :^ShpObject, attrIndex : int) -> (f64, ShpValState)
get_date_value :: proc( obj :^ShpObject, attrIndex : int) -> (time.Time, ShpValState)
is_null_value :: proc( obj :^ShpObject, attrIndex : int) -> bool
Open for write, will always create new files, overwriting existing ones.
create_files :: proc( filePath : string, shpType :ShpType, epsgCode: string = "") -> (^ShpHandle, os.Error)
Adding database fields:
add_field :: proc( handle :^ShpHandle,
name : string,
type : u8, // dbf... constants
width : u8 = 0,
decimals : u8 = 0)
This must be done before writing objects to the files
close_and_dispose :: proc (handle : ^^ShpHandle)
Closes the files, and frees the memory. Sets the handle to nil.
create_obj :: proc ( shpType : ShpType) -> ^ShpObject
You must add all attributes to the record, the number of values added must be the same as the number of attribute fields added.
add_string_value :: proc ( obj :^ShpObject, strValue : string)
add_int_value :: proc ( obj :^ShpObject, intValue : int)
add_float_value :: proc ( obj :^ShpObject, floatVal : f64)
add_date_value :: proc ( obj :^ShpObject, date : time.Time)
add_null_value :: proc ( obj :^ShpObject)
Setting coordinates of point object:
set_point_xy :: proc( obj: ^ShpObject, x, y: f64)
Adding coordinates of other shape types
add_part :: proc( obj: ^ShpObject, xyData :[][2]f64)
add_hole :: proc( obj: ^ShpObject, xyData :[][2]f64)
You can only add holes to area shapes, and you cannot add a hole unless you've added a part first.
xyData is an array of x and y pairs.
dispose_obj :: proc( obj : ^^ShpObject)
To free memory of object and set obj to nil
write_obj :: proc( handle :^ShpHandle, obj: ^ShpObject) -> os.Error
get_epsg_code :: proc( handle :^ShpHandle) -> (string, ok: bool)
If the .prj file contains an AUTHORITY element with the EPSG-code, returns the code with ok = true if exists, but ok = false if an EPSG-code is not found, despite the fact that there is a .prj-file.
import "<path to shp>"
read_shape_file proc :: (filePath :string)
{
handle, err := shp.open_files( filePath)
assert (err == nil)
defer shp.close_and_dispose( &handle)
obj : ^shp.ShpObject
obj, err = shp.read_first_obj( handle)
for err == nil {
do_something( obj)
shp.dispose_obj( &obj)
obj, err = shp.read_next_obj( handle)
}
}
import "<path to shp>"
import "core:time"
write_to_file proc :: (filePath :string)
{
handle, err := shp.create_files( filePath, shp.ShpType.point, "EPSG:4326")
defer close_and_dispose( &handle)
assert( err == nil)
shp.add_field( handle, "id", shp.dbfNumberAttribute, 5)
shp.add_field( handle, "name", shp.dbfStringAttribute, 12)
shp.add_field( handle, "area", shp.dbfNumberAttribute, 15, 2)
shp.add_field( handle, "date", shp.dbfDateAttribute)
obj := shp.create_obj( shp.ShpType.point)
defer shp.dispose_obj( &obj)
assert(obj != nil)
shp.add_int_value( obj, 123) // id
shp.add_string_value( obj, "Trondheim") // name
shp.add_float_value( obj, 342.2) // area (sq.km)
shp.add_date_value( obj, time.now()) // date
shp.set_point_xy( obj, 10.395916, 63.433412) // only for point features
// shp.add_part(...) for other types.
// shp.add_hole(...) to add holes in polygon
err = shp.write_obj( handle, obj)
assert( err == nil)
}
Agnar Renolen agnar.renolen@gmail.com