Skip to content

Commit a91d3b9

Browse files
authored
Merge pull request #93 from GeospatialPython/karimbahgat-patch-dtypesdocs
Docs improvements and dtype fixes
2 parents d67c8fc + ac87e6f commit a91d3b9

14 files changed

+166
-59
lines changed

README.md

+158-53
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,24 @@
11
## Contents
22

3-
[TOC]
3+
[Overview](#overview)
4+
5+
[Examples](#examples)
6+
- [Reading Shapefiles](#reading-shapefiles)
7+
- [Reading Shapefiles from File-Like Objects](#reading-shapefiles-from-file-like-objects)
8+
- [Reading Geometry](#reading-geometry)
9+
- [Reading Records](#reading-records)
10+
- [Reading Geometry and Records Simultaneously](#reading-geometry-and-records-simultaneously)
11+
- [Writing Shapefiles](#writing-shapefiles)
12+
- [Setting the Shape Type](#setting-the-shape-type)
13+
- [Geometry and Record Balancing](#geometry-and-record-balancing)
14+
- [Adding Geometry](#adding-geometry)
15+
- [Creating Attributes](#creating-attributes)
16+
- [File Names](#file-names)
17+
- [Saving to File-Like Objects](#saving-to-file-like-objects)
18+
- [Editing Shapefiles](#editing-shapefiles)
19+
- [Geometry and Record Balancing](#geometry-and-record-balancing)
20+
- [Python \_\_geo_interface\_\_](#python-\_\_geo\_interface\_\_)
21+
- [Testing](#testing)
422

523
# Overview
624

@@ -385,17 +403,17 @@ data lines up with the geometry data. For example:
385403
### Adding Geometry
386404

387405
Geometry is added using one of three methods: "null", "point", or "poly". The
388-
"null" method is used for null shapes, "point" is used for point shapes, and
389-
"poly" is used for everything else.
406+
"null" method is used for null shapes, "point" is used for point shapes, "line" for lines, and
407+
"poly" is used for polygons and everything else.
390408

391409
**Adding a Point shape**
392410

393411
Point shapes are added using the "point" method. A point is specified by an x,
394412
y, and optional z (elevation) and m (measure) value.
395413

396414

397-
>>> w = shapefile.Writer()
398-
415+
>>> w = shapefile.Writer(shapefile.POINTM)
416+
399417
>>> w.point(122, 37) # No elevation or measure values
400418

401419
>>> w.shapes()[0].points
@@ -405,21 +423,45 @@ y, and optional z (elevation) and m (measure) value.
405423

406424
>>> w.shapes()[1].points
407425
[[118, 36, 4, 8]]
426+
427+
>>> w.field('FIRST_FLD', 'C')
428+
>>> w.field('SECOND_FLD', 'C')
429+
430+
>>> w.save('shapefiles/test/point')
408431

409-
**Adding a Poly shape**
432+
**Adding a Polygon shape**
410433

411-
"Poly" shapes can be either polygons or lines. Shapefile polygons must have at
434+
Shapefile polygons must have at
412435
least 4 points and the last point must be the same as the first. PyShp
413-
automatically enforces closed polygons. A line must have at least two points.
414-
Because of the similarities between these two shape types they are created
415-
using a single method called "poly".
436+
automatically enforces closed polygons.
416437

417438

418439
>>> w = shapefile.Writer()
419440

420-
>>> w.poly(shapeType=3, parts=[[[122,37,4,9], [117,36,3,4]], [[115,32,8,8],
441+
>>> w.poly(parts=[[[122,37,4,9], [117,36,3,4]], [[115,32,8,8],
421442
... [118,20,6,4], [113,24]]])
422443

444+
>>> w.field('FIRST_FLD', 'C')
445+
>>> w.field('SECOND_FLD', 'C')
446+
447+
>>> w.save('shapefiles/test/polygon')
448+
449+
**Adding a Line shape**
450+
451+
A line must have at least two points.
452+
Because of the similarities between polygon and line types it is possible to create
453+
a line shape using either the "line" or "poly" method.
454+
455+
>>> w = shapefile.Writer()
456+
457+
>>> w.line(parts=[[[1,5],[5,5],[5,1],[3,3],[1,1]]])
458+
>>> w.poly(parts=[[[1,3],[5,3]]], shapeType=shapefile.POLYLINE)
459+
460+
>>> w.field('FIRST_FLD', 'C')
461+
>>> w.field('SECOND_FLD', 'C')
462+
463+
>>> w.save('shapefiles/test/polygon')
464+
423465
**Adding a Null shape**
424466

425467
Because Null shape types (shape type 0) have no geometry the "null" method is
@@ -439,51 +481,114 @@ The writer object's shapes list will now have one null shape:
439481

440482
Creating attributes involves two steps. Step 1 is to create fields to contain
441483
attribute values and step 2 is to populate the fields with values for each
442-
shape record.
443-
444-
The following attempts to create a complete shapefile. The attribute and
445-
field names are not very creative:
446-
447-
448-
>>> w = shapefile.Writer(shapefile.POINT)
449-
>>> w.point(1,1)
450-
>>> w.point(3,1)
451-
>>> w.point(4,3)
452-
>>> w.point(2,2)
453-
>>> w.field('FIRST_FLD')
454-
>>> w.field('SECOND_FLD','C','40')
455-
>>> w.record('First','Point')
456-
>>> w.record('Second','Point')
457-
>>> w.record('Third','Point')
458-
>>> w.record('Fourth','Point')
459-
>>> w.save('shapefiles/test/point')
460-
461-
>>> w = shapefile.Writer(shapefile.POLYGON)
462-
>>> w.poly(parts=[[[1,5],[5,5],[5,1],[3,3],[1,1]]])
463-
>>> w.field('FIRST_FLD','C','40')
464-
>>> w.field('SECOND_FLD','C','40')
465-
>>> w.record('First','Polygon')
466-
>>> w.save('shapefiles/test/polygon')
467-
468-
>>> w = shapefile.Writer(shapefile.POLYLINE)
469-
>>> w.line(parts=[[[1,5],[5,5],[5,1],[3,3],[1,1]]])
470-
>>> w.poly(parts=[[[1,3],[5,3]]], shapeType=shapefile.POLYLINE)
471-
>>> w.field('FIRST_FLD','C','40')
472-
>>> w.field('SECOND_FLD','C','40')
473-
>>> w.record('First','Line')
474-
>>> w.record('Second','Line')
475-
>>> w.save('shapefiles/test/line')
484+
shape record.
476485

477-
You can also add attributes using keyword arguments where the keys are field
478-
names.
486+
There are several different field types, all of which support storing None values as NULL.
479487

488+
Text fields are created using the 'C' type, and the third 'size' argument can be customized to the expected
489+
length of text values to save space:
480490

481-
>>> w = shapefile.Writer(shapefile.POLYLINE)
482-
>>> w.line(parts=[[[1,5],[5,5],[5,1],[3,3],[1,1]]])
483-
>>> w.field('FIRST_FLD','C','40')
484-
>>> w.field('SECOND_FLD','C','40')
485-
>>> w.record(FIRST_FLD='First', SECOND_FLD='Line')
486-
>>> w.save('shapefiles/test/line')
491+
492+
>>> w = shapefile.Writer()
493+
>>> w.field('TEXT', 'C')
494+
>>> w.field('SHORT_TEXT', 'C', size=5)
495+
>>> w.field('LONG_TEXT', 'C', size=250)
496+
>>> w.null()
497+
>>> w.record('Hello', 'World', 'World'*50)
498+
>>> w.save('shapefiles/test/dtype')
499+
500+
>>> r = shapefile.Reader('shapefiles/test/dtype')
501+
>>> assert r.record(0) == ['Hello', 'World', 'World'*50]
502+
503+
Date fields are created using the 'D' type, and can be created using either
504+
date objects, lists, or a YYYYMMDD formatted string.
505+
Field length or decimal have no impact on this type:
506+
507+
508+
>>> from datetime import date
509+
>>> w = shapefile.Writer()
510+
>>> w.field('DATE', 'D')
511+
>>> w.null()
512+
>>> w.null()
513+
>>> w.null()
514+
>>> w.null()
515+
>>> w.record(date(1998,1,30))
516+
>>> w.record([1998,1,30])
517+
>>> w.record('19980130')
518+
>>> w.record(None)
519+
>>> w.save('shapefiles/test/dtype')
520+
521+
>>> r = shapefile.Reader('shapefiles/test/dtype')
522+
>>> assert r.record(0) == [date(1998,1,30)]
523+
>>> assert r.record(1) == [date(1998,1,30)]
524+
>>> assert r.record(2) == [date(1998,1,30)]
525+
>>> assert r.record(3) == [None]
526+
527+
Numeric fields are created using the 'N' type (or the 'F' type, which is exactly the same).
528+
By default the fourth decimal argument is set to zero, essentially creating an integer field.
529+
To store floats you must set the decimal argument to the precision of your choice.
530+
To store very large numbers you must increase the field length size to the total number of digits
531+
(including comma and minus).
532+
533+
534+
>>> w = shapefile.Writer()
535+
>>> w.field('INT', 'N')
536+
>>> w.field('LOWPREC', 'N', decimal=2)
537+
>>> w.field('MEDPREC', 'N', decimal=10)
538+
>>> w.field('HIGHPREC', 'N', decimal=30)
539+
>>> w.field('FTYPE', 'F', decimal=10)
540+
>>> w.field('LARGENR', 'N', 101)
541+
>>> nr = 1.3217328
542+
>>> w.null()
543+
>>> w.null()
544+
>>> w.record(INT=int(nr), LOWPREC=nr, MEDPREC=nr, HIGHPREC=-3.2302e-25, FTYPE=nr, LARGENR=int(nr)*10**100)
545+
>>> w.record(None, None, None, None, None, None)
546+
>>> w.save('shapefiles/test/dtype')
547+
548+
>>> r = shapefile.Reader('shapefiles/test/dtype')
549+
>>> r.record(0)
550+
[1, 1.32, 1.3217328, -3.2302e-25, 1.3217328, 10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000L]
551+
>>> r.record(1)
552+
[None, None, None, None, None, None]
553+
554+
555+
Finally, we can create boolean fields by setting the type to 'L'.
556+
This field can take True or False values, or any string whose first character is one of YyTt (True) or NnFf (False).
557+
None is interpreted as missing.
558+
559+
560+
>>> w = shapefile.Writer()
561+
>>> w.field('BOOLEAN', 'L')
562+
>>> w.null()
563+
>>> w.null()
564+
>>> w.null()
565+
>>> w.null()
566+
>>> w.null()
567+
>>> w.record(True)
568+
>>> w.record("Yes")
569+
>>> w.record(False)
570+
>>> w.record("No")
571+
>>> w.record(None)
572+
>>> w.save('shapefiles/test/dtype')
573+
574+
>>> r = shapefile.Reader('shapefiles/test/dtype')
575+
>>> assert r.record(0) == [True]
576+
>>> assert r.record(1) == [True]
577+
>>> assert r.record(2) == [False]
578+
>>> assert r.record(3) == [False]
579+
>>> assert r.record(4) == [None]
580+
581+
582+
You can also add attributes using keyword arguments where the keys are field names.
583+
584+
>>> w = shapefile.Writer()
585+
>>> w.field('FIRST_FLD','C','40')
586+
>>> w.field('SECOND_FLD','C','40')
587+
>>> w.record('First', 'Line')
588+
>>> w.record(FIRST_FLD='First', SECOND_FLD='Line')
589+
>>> assert w.records[0] == w.records[1]
590+
591+
487592

488593
### File Names
489594

shapefile.py

+8-6
Original file line numberDiff line numberDiff line change
@@ -926,15 +926,17 @@ def __dbfRecords(self):
926926
for record in self.records:
927927
if not self.fields[0][0].startswith("Deletion"):
928928
f.write(b(' ')) # deletion flag
929-
for (fieldName, fieldType, size, dec), value in zip(self.fields, record):
929+
for (fieldName, fieldType, size, deci), value in zip(self.fields, record):
930930
fieldType = fieldType.upper()
931931
size = int(size)
932932
if fieldType in ("N","F"):
933933
# numeric or float: number stored as a string, right justified, and padded with blanks to the width of the field.
934934
if value in MISSING:
935935
value = str("*"*size) # QGIS NULL
936+
elif not deci:
937+
value = format(value, "d")[:size].rjust(size) # caps the size if exceeds the field size
936938
else:
937-
value = str(value).rjust(size)
939+
value = format(value, ".%sf"%deci)[:size].rjust(size) # caps the size if exceeds the field size
938940
elif fieldType == "D":
939941
# date: 8 bytes - date stored as a string in the format YYYYMMDD.
940942
if isinstance(value, date):
@@ -946,7 +948,7 @@ def __dbfRecords(self):
946948
elif isinstance(value, str) and len(value) == 8:
947949
pass # value is already a date string
948950
else:
949-
raise ShapefileException("Date values must be either a datetime.date object, a list, or a missing value of None or ''.")
951+
raise ShapefileException("Date values must be either a datetime.date object, a list, a YYYYMMDD string, or a missing value.")
950952
elif fieldType == 'L':
951953
# logical: 1 byte - initialized to 0x20 (space) otherwise T or F.
952954
if value in MISSING:
@@ -967,9 +969,9 @@ def null(self):
967969
"""Creates a null shape."""
968970
self._shapes.append(_Shape(NULL))
969971

970-
def point(self, x, y, z=0, m=0):
972+
def point(self, x, y, z=0, m=0, shapeType=POINT):
971973
"""Creates a point shape."""
972-
pointShape = _Shape(self.shapeType)
974+
pointShape = _Shape(shapeType)
973975
pointShape.points.append([x, y, z, m])
974976
self._shapes.append(pointShape)
975977

@@ -1060,7 +1062,7 @@ def saveShp(self, target):
10601062
target = os.path.splitext(target)[0] + '.shp'
10611063
if self.shapeType is None:
10621064
# autoset file type to first non-null geometry
1063-
self.shapeType = next((s.shapeType for s in self._shapes if s.shapeType != NULL), NULL)
1065+
self.shapeType = next((s.shapeType for s in self._shapes if s.shapeType), NULL)
10641066
self.shp = self.__getFileObj(target)
10651067
self.__shapefileHeader(self.shp, headerType='shp')
10661068
self.__shpRecords()

shapefiles/test/dtype.dbf

75 Bytes
Binary file not shown.

shapefiles/test/dtype.shp

160 Bytes
Binary file not shown.

shapefiles/test/dtype.shx

140 Bytes
Binary file not shown.

shapefiles/test/line.dbf

1.99 KB
Binary file not shown.

shapefiles/test/line.shp

3.24 KB
Binary file not shown.

shapefiles/test/line.shx

292 Bytes
Binary file not shown.

shapefiles/test/point.dbf

299 Bytes
Binary file not shown.

shapefiles/test/point.shp

156 Bytes
Binary file not shown.

shapefiles/test/point.shx

116 Bytes
Binary file not shown.

shapefiles/test/polygon.dbf

198 Bytes
Binary file not shown.

shapefiles/test/polygon.shp

324 Bytes
Binary file not shown.

shapefiles/test/polygon.shx

116 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)