Skip to content

Commit 7d6a57a

Browse files
author
Jeff Whitaker
committed
Merge pull request #119 from jswhit/master
update MANIFEST.in, update URLs in examples, update shapefile.py from pyshp svn
2 parents 15e9230 + 7903b85 commit 7d6a57a

File tree

5 files changed

+166
-41
lines changed

5 files changed

+166
-41
lines changed

MANIFEST.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ include examples/utmtest.py
9191
include examples/test_rotpole.py
9292
include examples/wm201_Arctic_JJA_1990-2008_moyenneDesMoyennes.nc
9393
include examples/README
94+
include lib/mpl_toolkits/__init__.py
9495
include lib/mpl_toolkits/basemap/__init__.py
9596
include lib/mpl_toolkits/basemap/proj.py
9697
include lib/mpl_toolkits/basemap/pyproj.py
40.7 KB
Binary file not shown.

doc/users/figures/plotargo.py

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,24 @@
1-
from netCDF4 import Dataset
1+
from netCDF4 import Dataset, num2date
22
import time, calendar, datetime, numpy
33
from mpl_toolkits.basemap import Basemap
44
import matplotlib.pyplot as plt
5-
def datetomsecs(d):
6-
"""convert from datetime to msecs since the unix epoch began"""
7-
return int(calendar.timegm(time.struct_time(d.timetuple()))*1000)
8-
# set date range
9-
date1 = datetime.datetime(2010,1,1,0)
10-
date2 = datetime.datetime(2010,1,8,0)
11-
t1 = datetomsecs(date1); t2 = datetomsecs(date2)
12-
# build constraint expression to get locations of floats in specified time
13-
# range.
14-
urlbase='http://dapper.pmel.noaa.gov/dapper/argo/argo_all.cdp'
15-
sel="?location.JULD,location.LATITUDE,location.LONGITUDE&location.JULD>%s&location.JULD<%s"%(t1,t2)
16-
# retrieve data
17-
dset = Dataset(urlbase+sel)
18-
lats = dset.variables['location.LATITUDE'][:]
19-
lons = dset.variables['location.LONGITUDE'][:]
5+
# data downloaded from
6+
# http://coastwatch.pfeg.noaa.gov/erddap/tabledap/apdrcArgoAll.html via
7+
# http://coastwatch.pfeg.noaa.gov/erddap/tabledap/apdrcArgoAll.nc?longitude,latitude,time&longitude>=0&longitude<=360&latitude>=-90&latitude<=90&time>=2010-01-01&time<=2010-01-08&distinct()
8+
dset = Dataset('apdrcArgoAll_af28_ee2b_1d10.nc')
9+
lats = dset.variables['latitude'][:]
10+
lons = dset.variables['longitude'][:]
11+
time = dset.variables['time']
12+
times = time[:]
13+
t1 = times.min(); t2 = times.max()
14+
date1 = num2date(t1, units=time.units)
15+
date2 = num2date(t2, units=time.units)
2016
# draw map with markers for float locations
2117
m = Basemap(projection='hammer',lon_0=180)
2218
x, y = m(lons,lats)
2319
m.drawmapboundary(fill_color='#99ffff')
2420
m.fillcontinents(color='#cc9966',lake_color='#99ffff')
2521
m.scatter(x,y,3,marker='o',color='k')
2622
plt.title('Locations of %s ARGO floats active between %s and %s' %\
27-
(len(lats),date1.strftime('%Y%m%d'),date2.strftime('%Y%m%d')))
23+
(len(lats),date1,date2),fontsize=12)
2824
plt.show()

doc/users/figures/plotsst.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
date = datetime(2007,12,15,0) # date to plot.
77
# open dataset.
88
dataset = \
9-
Dataset('http://www.ncdc.noaa.gov/thredds/dodsC/OISST-V2-AVHRR-AMSR_agg')
9+
Dataset('http://www.ncdc.noaa.gov/thredds/dodsC/OISST-V2-AVHRR_agg')
1010
timevar = dataset.variables['time']
1111
timeindex = date2index(date,timevar) # find time index for desired date.
1212
# read sst. Will automatically create a masked array using

lib/mpl_toolkits/basemap/shapefile.py

Lines changed: 151 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,20 @@
22
shapefile.py
33
Provides read and write support for ESRI Shapefiles.
44
author: jlawhead<at>geospatialpython.com
5-
date: 20110927
6-
version: 1.1.4
5+
date: 20130622
6+
version: 1.1.7
77
Compatible with Python versions 2.4-3.x
88
"""
9+
__version__ = "1.1.7"
910

1011
from struct import pack, unpack, calcsize, error
1112
import os
1213
import sys
1314
import time
1415
import array
16+
import tempfile
1517
#
1618
# Constants for shape types
17-
default_encoding = 'utf-8'
1819
NULL = 0
1920
POINT = 1
2021
POLYLINE = 3
@@ -32,11 +33,14 @@
3233

3334
PYTHON3 = sys.version_info[0] == 3
3435

36+
if PYTHON3:
37+
xrange = range
38+
3539
def b(v):
3640
if PYTHON3:
3741
if isinstance(v, str):
3842
# For python 3 encode str to bytes.
39-
return v.encode(default_encoding)
43+
return v.encode('utf-8')
4044
elif isinstance(v, bytes):
4145
# Already bytes.
4246
return v
@@ -51,7 +55,7 @@ def u(v):
5155
if PYTHON3:
5256
if isinstance(v, bytes):
5357
# For python 3 decode bytes to str.
54-
return v.decode(default_encoding)
58+
return v.decode('utf-8')
5559
elif isinstance(v, str):
5660
# Already str.
5761
return v
@@ -74,6 +78,16 @@ class _Array(array.array):
7478
def __repr__(self):
7579
return str(self.tolist())
7680

81+
def signed_area(coords):
82+
"""Return the signed area enclosed by a ring using the linear time
83+
algorithm at http://www.cgafaq.info/wiki/Polygon_Area. A value <= 0
84+
indicates a counter-clockwise oriented ring.
85+
"""
86+
xs, ys = map(list, zip(*coords))
87+
xs.append(xs[1])
88+
ys.append(ys[1])
89+
return sum(xs[i]*(ys[i+1]-ys[i-1]) for i in range(1, len(coords)))/2.0
90+
7791
class _Shape:
7892
def __init__(self, shapeType=None):
7993
"""Stores the geometry of the different shape types
@@ -88,6 +102,78 @@ def __init__(self, shapeType=None):
88102
self.shapeType = shapeType
89103
self.points = []
90104

105+
@property
106+
def __geo_interface__(self):
107+
if self.shapeType in [POINT, POINTM, POINTZ]:
108+
return {
109+
'type': 'Point',
110+
'coordinates': tuple(self.points[0])
111+
}
112+
elif self.shapeType in [MULTIPOINT, MULTIPOINTM, MULTIPOINTZ]:
113+
return {
114+
'type': 'MultiPoint',
115+
'coordinates': tuple([tuple(p) for p in self.points])
116+
}
117+
elif self.shapeType in [POLYLINE, POLYLINEM, POLYLINEZ]:
118+
if len(self.parts) == 1:
119+
return {
120+
'type': 'LineString',
121+
'coordinates': tuple([tuple(p) for p in self.points])
122+
}
123+
else:
124+
ps = None
125+
coordinates = []
126+
for part in self.parts:
127+
if ps == None:
128+
ps = part
129+
continue
130+
else:
131+
coordinates.append(tuple([tuple(p) for p in self.points[ps:part]]))
132+
ps = part
133+
else:
134+
coordinates.append(tuple([tuple(p) for p in self.points[part:]]))
135+
return {
136+
'type': 'MultiLineString',
137+
'coordinates': tuple(coordinates)
138+
}
139+
elif self.shapeType in [POLYGON, POLYGONM, POLYGONZ]:
140+
if len(self.parts) == 1:
141+
return {
142+
'type': 'Polygon',
143+
'coordinates': (tuple([tuple(p) for p in self.points]),)
144+
}
145+
else:
146+
ps = None
147+
coordinates = []
148+
for part in self.parts:
149+
if ps == None:
150+
ps = part
151+
continue
152+
else:
153+
coordinates.append(tuple([tuple(p) for p in self.points[ps:part]]))
154+
ps = part
155+
else:
156+
coordinates.append(tuple([tuple(p) for p in self.points[part:]]))
157+
polys = []
158+
poly = [coordinates[0]]
159+
for coord in coordinates[1:]:
160+
if signed_area(coord) < 0:
161+
polys.append(poly)
162+
poly = [coord]
163+
else:
164+
poly.append(coord)
165+
polys.append(poly)
166+
if len(polys) == 1:
167+
return {
168+
'type': 'Polygon',
169+
'coordinates': tuple(polys[0])
170+
}
171+
elif len(polys) > 1:
172+
return {
173+
'type': 'MultiPolygon',
174+
'coordinates': polys
175+
}
176+
91177
class _ShapeRecord:
92178
"""A shape object of any type."""
93179
def __init__(self, shape=None, record=None):
@@ -128,7 +214,7 @@ def __init__(self, *args, **kwargs):
128214
self.__dbfHdrLength = 0
129215
# See if a shapefile name was passed as an argument
130216
if len(args) > 0:
131-
if type(args[0]) is type("stringTest"):
217+
if is_string(args[0]):
132218
self.load(args[0])
133219
return
134220
if "shp" in kwargs.keys():
@@ -221,6 +307,8 @@ def __shape(self):
221307
record = _Shape()
222308
nParts = nPoints = zmin = zmax = mmin = mmax = None
223309
(recNum, recLength) = unpack(">2i", f.read(8))
310+
# Determine the start of the next record
311+
next = f.tell() + (2 * recLength)
224312
shapeType = unpack("<i", f.read(4))[0]
225313
record.shapeType = shapeType
226314
# For Null shapes create an empty points list for consistency
@@ -248,12 +336,12 @@ def __shape(self):
248336
if shapeType in (13,15,18,31):
249337
(zmin, zmax) = unpack("<2d", f.read(16))
250338
record.z = _Array('d', unpack("<%sd" % nPoints, f.read(nPoints * 8)))
251-
# Read m extremes and values
252-
if shapeType in (13,15,18,23,25,28,31):
339+
# Read m extremes and values if header m values do not equal 0.0
340+
if shapeType in (13,15,18,23,25,28,31) and not 0.0 in self.measure:
253341
(mmin, mmax) = unpack("<2d", f.read(16))
254342
# Measure values less than -10e38 are nodata values according to the spec
255343
record.m = []
256-
for m in _Array('d', unpack("%sd" % nPoints, f.read(nPoints * 8))):
344+
for m in _Array('d', unpack("<%sd" % nPoints, f.read(nPoints * 8))):
257345
if m > -10e38:
258346
record.m.append(m)
259347
else:
@@ -267,6 +355,10 @@ def __shape(self):
267355
# Read a single M value
268356
if shapeType in (11,21):
269357
record.m = unpack("<d", f.read(8))
358+
# Seek to the end of this record as defined by the record header because
359+
# the shapefile spec doesn't require the actual content to meet the header
360+
# definition. Probably allowed for lazy feature deletion.
361+
f.seek(next)
270362
return record
271363

272364
def __shapeIndex(self, i=None):
@@ -296,9 +388,10 @@ def shape(self, i=0):
296388
i = self.__restrictIndex(i)
297389
offset = self.__shapeIndex(i)
298390
if not offset:
299-
# Shx index not available so use the full list.
300-
shapes = self.shapes()
301-
return shapes[i]
391+
# Shx index not available so iterate the full list.
392+
for j,k in enumerate(self.iterShapes()):
393+
if j == i:
394+
return k
302395
shp.seek(offset)
303396
return self.__shape()
304397

@@ -311,6 +404,14 @@ def shapes(self):
311404
shapes.append(self.__shape())
312405
return shapes
313406

407+
def iterShapes(self):
408+
"""Serves up shapes in a shapefile as an iterator. Useful
409+
for handling large shapefiles."""
410+
shp = self.__getFileObj(self.shp)
411+
shp.seek(100)
412+
while shp.tell() < self.shpLength:
413+
yield self.__shape()
414+
314415
def __dbfHeaderLength(self):
315416
"""Retrieves the header length of a dbf file header."""
316417
if not self.__dbfHdrLength:
@@ -416,12 +517,23 @@ def records(self):
416517
records.append(r)
417518
return records
418519

520+
def iterRecords(self):
521+
"""Serves up records in a dbf file as an iterator.
522+
Useful for large shapefiles or dbf files."""
523+
if not self.numRecords:
524+
self.__dbfHeader()
525+
f = self.__getFileObj(self.dbf)
526+
f.seek(self.__dbfHeaderLength())
527+
for i in xrange(self.numRecords):
528+
r = self.__record()
529+
if r:
530+
yield r
531+
419532
def shapeRecord(self, i=0):
420533
"""Returns a combination geometry and attribute record for the
421534
supplied record index."""
422535
i = self.__restrictIndex(i)
423-
return _ShapeRecord(shape=self.shape(i),
424-
record=self.record(i))
536+
return _ShapeRecord(shape=self.shape(i), record=self.record(i))
425537

426538
def shapeRecords(self):
427539
"""Returns a list of combination geometry/attribute records for
@@ -675,7 +787,8 @@ def __shpRecords(self):
675787
except error:
676788
raise ShapefileException("Failed to write elevation extremes for record %s. Expected floats." % recNum)
677789
try:
678-
[f.write(pack("<d", p[2])) for p in s.points]
790+
#[f.write(pack("<d", p[2])) for p in s.points]
791+
f.write(pack("<%sd" % len(s.z), *s.z))
679792
except error:
680793
raise ShapefileException("Failed to write elevation values for record %s. Expected floats." % recNum)
681794
# Write m extremes and values
@@ -768,7 +881,9 @@ def poly(self, parts=[], shapeType=POLYGON, partTypes=[]):
768881
polyShape.parts = []
769882
polyShape.points = []
770883
for part in parts:
771-
polyShape.parts.append(len(polyShape.points))
884+
# Make sure polygon is closed
885+
if shapeType in (5,15,25,31) and part[0] != part[-1]:
886+
part.append(part[0])
772887
for point in part:
773888
# Ensure point is list
774889
if not isinstance(point, list):
@@ -777,6 +892,7 @@ def poly(self, parts=[], shapeType=POLYGON, partTypes=[]):
777892
while len(point) < 4:
778893
point.append(0)
779894
polyShape.points.append(point)
895+
polyShape.parts.append(len(polyShape.points))
780896
if polyShape.shapeType == 31:
781897
if not partTypes:
782898
for part in parts:
@@ -806,10 +922,10 @@ def record(self, *recordList, **recordDict):
806922
for field in self.fields:
807923
if field[0] in recordDict:
808924
val = recordDict[field[0]]
809-
if val:
810-
record.append(val)
811-
else:
925+
if val is None:
812926
record.append("")
927+
else:
928+
record.append(val)
813929
if record:
814930
self.records.append(record)
815931

@@ -851,21 +967,33 @@ def saveDbf(self, target):
851967
def save(self, target=None, shp=None, shx=None, dbf=None):
852968
"""Save the shapefile data to three files or
853969
three file-like objects. SHP and DBF files can also
854-
be written exclusively using saveShp, saveShx, and saveDbf respectively."""
855-
# TODO: Create a unique filename for target if None.
970+
be written exclusively using saveShp, saveShx, and saveDbf respectively.
971+
If target is specified but not shp,shx, or dbf then the target path and
972+
file name are used. If no options or specified, a unique base file name
973+
is generated to save the files and the base file name is returned as a
974+
string.
975+
"""
976+
# Create a unique file name if one is not defined
856977
if shp:
857978
self.saveShp(shp)
858979
if shx:
859980
self.saveShx(shx)
860981
if dbf:
861982
self.saveDbf(dbf)
862-
elif target:
983+
elif not shp and not shx and not dbf:
984+
generated = False
985+
if not target:
986+
temp = tempfile.NamedTemporaryFile(prefix="shapefile_",dir=os.getcwd())
987+
target = temp.name
988+
generated = True
863989
self.saveShp(target)
864990
self.shp.close()
865991
self.saveShx(target)
866992
self.shx.close()
867993
self.saveDbf(target)
868994
self.dbf.close()
995+
if generated:
996+
return target
869997

870998
class Editor(Writer):
871999
def __init__(self, shapefile=None, shapeType=POINT, autoBalance=1):
@@ -992,7 +1120,7 @@ def test():
9921120

9931121
if __name__ == "__main__":
9941122
"""
995-
Doctests are contained in the module 'pyshp_usage.py'. This library was developed
1123+
Doctests are contained in the file 'README.txt'. This library was originally developed
9961124
using Python 2.3. Python 2.4 and above have some excellent improvements in the built-in
9971125
testing libraries but for now unit testing is done using what's available in
9981126
2.3.

0 commit comments

Comments
 (0)