├── data
├── g1k15.dbf
├── g1k15.shp
├── g1k15.shx
├── g2g15.dbf
├── g2g15.shp
├── g2g15.shx
├── Conditions_d'exploitation-Copyright_OFS_GEOSTAT.pdf
└── data_sources.txt
├── img
├── switzerland_zip.png
└── switzerland_altitude.png
├── ShapefileTests
├── Kantone
│ ├── Kantone.dbf
│ ├── Kantone.sbn
│ ├── Kantone.sbx
│ ├── Kantone.shp
│ ├── Kantone.shx
│ ├── Kantone.prj
│ └── Kantone.shp.xml
├── Info.plist
└── ShapefileTests.swift
├── Shapefile.xcodeproj
├── project.xcworkspace
│ └── contents.xcworkspacedata
└── project.pbxproj
├── LICENSE
├── .gitignore
├── README.md
├── vendor
├── CanvasView.swift
├── X11Colors.swift
├── BinUtils.swift
└── BitmapCanvas.swift
└── Shapefile
├── ShapefileBitmap.swift
├── ShapefileView.swift
├── main.swift
└── Shapefile.swift
/data/g1k15.dbf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nst/ShapefileReader/master/data/g1k15.dbf
--------------------------------------------------------------------------------
/data/g1k15.shp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nst/ShapefileReader/master/data/g1k15.shp
--------------------------------------------------------------------------------
/data/g1k15.shx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nst/ShapefileReader/master/data/g1k15.shx
--------------------------------------------------------------------------------
/data/g2g15.dbf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nst/ShapefileReader/master/data/g2g15.dbf
--------------------------------------------------------------------------------
/data/g2g15.shp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nst/ShapefileReader/master/data/g2g15.shp
--------------------------------------------------------------------------------
/data/g2g15.shx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nst/ShapefileReader/master/data/g2g15.shx
--------------------------------------------------------------------------------
/img/switzerland_zip.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nst/ShapefileReader/master/img/switzerland_zip.png
--------------------------------------------------------------------------------
/img/switzerland_altitude.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nst/ShapefileReader/master/img/switzerland_altitude.png
--------------------------------------------------------------------------------
/ShapefileTests/Kantone/Kantone.dbf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nst/ShapefileReader/master/ShapefileTests/Kantone/Kantone.dbf
--------------------------------------------------------------------------------
/ShapefileTests/Kantone/Kantone.sbn:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nst/ShapefileReader/master/ShapefileTests/Kantone/Kantone.sbn
--------------------------------------------------------------------------------
/ShapefileTests/Kantone/Kantone.sbx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nst/ShapefileReader/master/ShapefileTests/Kantone/Kantone.sbx
--------------------------------------------------------------------------------
/ShapefileTests/Kantone/Kantone.shp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nst/ShapefileReader/master/ShapefileTests/Kantone/Kantone.shp
--------------------------------------------------------------------------------
/ShapefileTests/Kantone/Kantone.shx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nst/ShapefileReader/master/ShapefileTests/Kantone/Kantone.shx
--------------------------------------------------------------------------------
/data/Conditions_d'exploitation-Copyright_OFS_GEOSTAT.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nst/ShapefileReader/master/data/Conditions_d'exploitation-Copyright_OFS_GEOSTAT.pdf
--------------------------------------------------------------------------------
/data/data_sources.txt:
--------------------------------------------------------------------------------
1 | data sources
2 | ------------
3 |
4 | http://www.bfs.admin.ch/bfs/portal/fr/index/dienstleistungen/geostat/datenbeschreibung/generalisierte_gemeindegrenzen.html
5 |
6 | http://www.taed.ch/dl/plz_p1.txt
7 |
--------------------------------------------------------------------------------
/Shapefile.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/ShapefileTests/Kantone/Kantone.prj:
--------------------------------------------------------------------------------
1 | PROJCS["CH1903_LV03",GEOGCS["GCS_CH1903",DATUM["D_CH1903",SPHEROID["Bessel_1841",6377397.155,299.1528128]],PRIMEM["Greenwich",0.0],UNIT["Degree",0.0174532925199433]],PROJECTION["Hotine_Oblique_Mercator_Azimuth_Center"],PARAMETER["False_Easting",600000.0],PARAMETER["False_Northing",200000.0],PARAMETER["Scale_Factor",1.0],PARAMETER["Azimuth",90.0],PARAMETER["Longitude_Of_Center",7.439583333333333],PARAMETER["Latitude_Of_Center",46.95240555555556],UNIT["Meter",1.0]]
--------------------------------------------------------------------------------
/ShapefileTests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 |
24 |
25 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Nicolas Seriot
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | ## Build generated
6 | build/
7 | DerivedData
8 |
9 | ## Various settings
10 | *.pbxuser
11 | !default.pbxuser
12 | *.mode1v3
13 | !default.mode1v3
14 | *.mode2v3
15 | !default.mode2v3
16 | *.perspectivev3
17 | !default.perspectivev3
18 | xcuserdata
19 |
20 | ## Other
21 | *.xccheckout
22 | *.moved-aside
23 | *.xcuserstate
24 | *.xcscmblueprint
25 |
26 | ## Obj-C/Swift specific
27 | *.hmap
28 | *.ipa
29 |
30 | ## Playgrounds
31 | timeline.xctimeline
32 | playground.xcworkspace
33 |
34 | # Swift Package Manager
35 | #
36 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
37 | # Packages/
38 | .build/
39 |
40 | # CocoaPods
41 | #
42 | # We recommend against adding the Pods directory to your .gitignore. However
43 | # you should judge for yourself, the pros and cons are mentioned at:
44 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
45 | #
46 | # Pods/
47 |
48 | # Carthage
49 | #
50 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
51 | # Carthage/Checkouts
52 |
53 | Carthage/Build
54 |
55 | # fastlane
56 | #
57 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
58 | # screenshots whenever they are needed.
59 | # For more information about the recommended setup visit:
60 | # https://github.com/fastlane/fastlane/blob/master/docs/Gitignore.md
61 |
62 | fastlane/report.xml
63 | fastlane/screenshots
64 |
--------------------------------------------------------------------------------
/ShapefileTests/ShapefileTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ShapefileTests.swift
3 | // ShapefileTests
4 | //
5 | // Created by nst on 20/03/16.
6 | // Copyright © 2016 Nicolas Seriot. All rights reserved.
7 | //
8 |
9 | import XCTest
10 |
11 | class ShapefileTests: XCTestCase {
12 |
13 | override func setUp() {
14 | super.setUp()
15 | // Put setup code here. This method is called before the invocation of each test method in the class.
16 | }
17 |
18 | override func tearDown() {
19 | // Put teardown code here. This method is called after the invocation of each test method in the class.
20 | super.tearDown()
21 | }
22 |
23 | // http://www.arcgis.com/home/item.html?id=a5067fb3b0b74b188d7b650fa5c64b39
24 |
25 | func testRecords() {
26 | let path = Bundle(for: ShapefileTests.self).path(forResource:"Kantone", ofType: "shp")!
27 |
28 | let sr = try! ShapefileReader(path:path)
29 |
30 | XCTAssertEqual(sr.dbf!.numberOfRecords, 26)
31 |
32 | let records = try! sr.dbf!.allRecords()
33 | XCTAssertEqual(records.count, 26)
34 |
35 | let rec = try! sr.dbf!.recordAtIndex(1)
36 | XCTAssertEqual(rec[1] as? String, "BE")
37 |
38 | print("\(#function) pass")
39 | }
40 |
41 | func testShapes() {
42 | let path = Bundle(for: ShapefileTests.self).path(forResource:"Kantone", ofType: "shp")!
43 |
44 | let sr = try! ShapefileReader(path:path)
45 |
46 | let shapes = sr.shp!.allShapes()
47 | XCTAssertEqual(shapes.count, 26)
48 |
49 | let shape2 = shapes[2]
50 | XCTAssertEqual(shape2.shapeType, ShapeType.polygon)
51 | XCTAssertEqual(shape2.parts.count, 5)
52 | XCTAssertEqual(shape2.points.count, 531)
53 | XCTAssert(shape2.bbox.x_max > 0)
54 |
55 | XCTAssertEqual(sr.shp.allShapes().count, try! sr.dbf!.allRecords().count)
56 | }
57 |
58 | func testShx() {
59 | let path = Bundle(for: ShapefileTests.self).path(forResource:"Kantone", ofType: "shp")!
60 |
61 | let sr = try! ShapefileReader(path:path)
62 |
63 | let offset = sr.shx!.shapeOffsetAtIndex(2)!
64 | let (_, shape2_) = try! sr.shp!.shapeAtOffset(UInt64(offset))!
65 |
66 | let shape2__ = sr.shp!.allShapes()[2]
67 | let shape2___ = sr[2]!
68 |
69 | XCTAssertEqual(shape2_.parts.count, shape2__.parts.count)
70 | XCTAssertEqual(shape2_.points.count, shape2__.points.count)
71 | XCTAssertEqual(shape2_.parts.count, shape2___.parts.count)
72 | XCTAssertEqual(shape2_.points.count, shape2___.points.count)
73 |
74 | XCTAssertEqual(sr.dbf!.numberOfRecords, sr.shx!.numberOfShapes)
75 | }
76 |
77 | }
78 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ShapefileReader
2 | __A shapefile reader in Swift__
3 |
4 | __Description__
5 |
6 | Reads data from files in [Shapefile](https://en.wikipedia.org/wiki/Shapefile) format.
7 |
8 | __API Overview__
9 |
10 | ShapefileReader is the only object you instantiate.
11 |
12 | Internally, it builds the three following objects, depending on the available auxiliary files:
13 | - `SHPReader` to read the shapes, ie the list of points
14 | - `DBFReader` to read the data records associated with the shapes
15 | - `SHXReader` to read the indices of the shapes, thus allowing direct access
16 |
17 | Most usages of `ShapefileReader` will remain like:
18 |
19 | ```swift
20 | guard let sr = ShapefileReader(path: "g1g15.shp") else { assertionFailure() }
21 |
22 | // iterate over both shapes and records
23 | for (shape, record) in sr.shapeAndRecordGenerator() {
24 | // record is [AnyObject]
25 |
26 | for points in shape.partPointsGenerator() {
27 | // draw polygon with [CGPoint]
28 | }
29 | }
30 | ```
31 |
32 | DBF API
33 |
34 | ```swift
35 | // if dbf file exists
36 | if let dbf = sr.dbf {
37 | // number of records
38 | let n = dbf.numberOfRecords
39 |
40 | // fields description
41 | print(dbf.fields)
42 |
43 | // iterate over records
44 | for r in dbf.recordGenerator() {
45 | // ...
46 | }
47 |
48 | // access a record by index
49 | print(dbf[1847])
50 | }
51 | ```
52 |
53 | [[DeletionFlag, C, 1, 0], [GMDNR, N, 9, 0], [GMDNAME, C, 50, 0], [BZNR, N, 9, 0], [KTNR, N, 9, 0], [GRNR, N, 9, 0], [AREA_HA, N, 9, 0], [X_MIN, N, 9, 0], [X_MAX, N, 9, 0], [Y_MIN, N, 9, 0], [Y_MAX, N, 9, 0], [X_CNTR, N, 9, 0], [Y_CNTR, N, 9, 0], [Z_MIN, N, 9, 0], [Z_MAX, N, 9, 0], [Z_AVG, N, 9, 0], [Z_MED, N, 9, 0], [Z_CNTR, N, 9, 0]]
54 | [5586, Lausanne, 2225, 22, 1, 4138, 534438, 544978, 150655, 161554, 538200, 152400, 371, 930, 670, 666, 585]
55 |
56 | SHX API
57 |
58 | ```swift
59 | // if index file exists
60 | if let shx = sr.shx {
61 | // access a shape by index
62 | if let offset = shx.shapeOffsetAtIndex(2) {
63 | if let (nextIndex, shape) = shp.shapeAtOffset(offset) {
64 | // use shape
65 | }
66 | }
67 | }
68 | ```
69 |
70 | or more simply
71 |
72 | ```swift
73 | let shape = sr[2]
74 | ```
75 |
76 | __Implementation Details__
77 |
78 | - shape points are CGPoint arrays
79 | - record are arrays of Int, Double, Bool or String (no NSDate, no optionals)
80 | - random access in files and enumerators are used each time it is possible
81 | - most of time, code will crash when files do not match the specs, feel free to open an issue an join the offending files
82 |
83 | __Tests and Drawing__
84 |
85 | The project comes with a unit test target.
86 |
87 | Also, it comes with `BitmapCanvas` and its subclass `BitmapCanvasShapefile` which will generate the following PNG file.
88 |
89 | You just need to change the path at the beginning of the `draw()` function in `main.swift` according the project's location.
90 |
91 |
92 |
93 |
94 |
95 | __TODO__
96 |
97 | Handle projection files, eg. from http://www.gadm.org/download.
98 |
99 |
--------------------------------------------------------------------------------
/ShapefileTests/Kantone/Kantone.shp.xml:
--------------------------------------------------------------------------------
1 |
2 | 20121217143810001.0TRUEFeatureClassToFeatureClass KANTONSGRENZEN C:\Users\phm\Documents\ArcGIS\Default.gdb Kantone # "KT "KT" true true false 2 Short 0 0 ,First,#,C:\Users\phm\Documents\ArcGIS\Packages\KANTON_POP\v101\schweiz.gdb\KANTONSGRENZEN,KANTONSGRENZEN.KT,-1,-1;AK "AK" true true false 2 Text 0 0 ,First,#,C:\Users\phm\Documents\ArcGIS\Packages\KANTON_POP\v101\schweiz.gdb\KANTONSGRENZEN,KANTONSGRENZEN.AK,-1,-1;Name "NAME_ANSI" true true false 23 Text 0 0 ,First,#,C:\Users\phm\Documents\ArcGIS\Packages\KANTON_POP\v101\schweiz.gdb\KANTONSGRENZEN,KANTONSGRENZEN.NAME_ANSI,-1,-1;Popdens "Popdens" true true false 4 Long 0 0 ,First,#,C:\Users\phm\Documents\ArcGIS\Packages\KANTON_POP\v101\schweiz.gdb\KANTONSGRENZEN,KANTONSGRENZEN.Popdens,-1,-1;Total "Total" true true false 4 Long 0 0 ,First,#,C:\Users\phm\Documents\ArcGIS\Packages\KANTON_POP\v101\schweiz.gdb\KANTONSGRENZEN,WB.csv.Total,-1,-1;J_0_19 "J_0_19" true true false 4 Long 0 0 ,First,#,C:\Users\phm\Documents\ArcGIS\Packages\KANTON_POP\v101\schweiz.gdb\KANTONSGRENZEN,WB.csv.J_0_19,-1,-1;J_20_39 "J_20_39" true true false 4 Long 0 0 ,First,#,C:\Users\phm\Documents\ArcGIS\Packages\KANTON_POP\v101\schweiz.gdb\KANTONSGRENZEN,WB.csv.J_20_39,-1,-1;J_40_64 "J_40_64" true true false 4 Long 0 0 ,First,#,C:\Users\phm\Documents\ArcGIS\Packages\KANTON_POP\v101\schweiz.gdb\KANTONSGRENZEN,WB.csv.J_40_64,-1,-1;J_65_79 "J_65_79" true true false 4 Long 0 0 ,First,#,C:\Users\phm\Documents\ArcGIS\Packages\KANTON_POP\v101\schweiz.gdb\KANTONSGRENZEN,WB.csv.J_65_79,-1,-1;J_80 "J_80" true true false 4 Long 0 0 ,First,#,C:\Users\phm\Documents\ArcGIS\Packages\KANTON_POP\v101\schweiz.gdb\KANTONSGRENZEN,WB.csv.J_80,-1,-1" #CalculateField Kantone Popdens "[Total] / [Shape_Area]" VB #CalculateField Kantone Popdens "[Total] / [Shape_Area]* 1000" VB #CalculateField Kantone Popdens "[Total] / [Shape_Area]* 1000" VB #CalculateField Kantone Popdens "[Total] / [Shape_Area]* 10000" VB #CalculateField Kantone Popdens "[Total] / [Shape_Area]* 10000" VB #CalculateField Kantone Popdens "[Total] / [Shape_Area]* 1000000" VB #CalculateField Kantone Popdens "[Total] / [Shape_Area]* 1000000" VB #
3 |
--------------------------------------------------------------------------------
/vendor/CanvasView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PDFCanvas.swift
3 | // Shapefile
4 | //
5 | // Created by nst on 25/03/16.
6 | // Copyright © 2016 Nicolas Seriot. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | class CanvasView : NSView {
12 |
13 | var cgContext : CGContext!
14 |
15 | fileprivate func degreesToRadians(_ x:CGFloat) -> CGFloat {
16 | return (M_PI * x / 180.0)
17 | }
18 |
19 | override func draw(_ dirtyRect: NSRect) {
20 | super.draw(dirtyRect)
21 |
22 | self.cgContext = unsafeBitCast(NSGraphicsContext.current()!.graphicsPort, to: CGContext.self)
23 | }
24 |
25 | func text(_ text:String, _ p:NSPoint, rotationRadians:CGFloat?, font : NSFont = NSFont(name: "Monaco", size: 10)!, color color_ : ConvertibleToNSColor = NSColor.black) {
26 |
27 | let color = color_.color
28 |
29 | let attr = [
30 | NSFontAttributeName:font,
31 | NSForegroundColorAttributeName:color
32 | ]
33 |
34 | cgContext.saveGState()
35 |
36 | if let radians = rotationRadians {
37 | cgContext.translateBy(x: p.x, y: p.y);
38 | cgContext.rotate(by: radians)
39 | cgContext.translateBy(x: -p.x, y: -p.y);
40 | }
41 |
42 | cgContext.scaleBy(x: 1.0, y: -1.0)
43 | cgContext.translateBy(x: 0.0, y: -2.0 * p.y - font.pointSize)
44 |
45 | text.draw(at: p, withAttributes: attr)
46 |
47 | cgContext.restoreGState()
48 | }
49 |
50 | func text(_ text:String, _ p:NSPoint, rotationDegrees degrees:CGFloat = 0.0, font : NSFont = NSFont(name: "Monaco", size: 10)!, color : ConvertibleToNSColor = NSColor.black) {
51 | self.text(text, p, rotationRadians: degreesToRadians(degrees), font: font, color: color)
52 | }
53 |
54 | func rectangle(_ rect:NSRect, stroke stroke_:ConvertibleToNSColor? = NSColor.black, fill fill_:ConvertibleToNSColor? = nil) {
55 |
56 | let stroke = stroke_?.color
57 | let fill = fill_?.color
58 |
59 | cgContext.saveGState()
60 |
61 | if let existingFillColor = fill {
62 | existingFillColor.setFill()
63 | NSBezierPath.fill(rect)
64 | }
65 |
66 | if let existingStrokeColor = stroke {
67 | existingStrokeColor.setStroke()
68 | NSBezierPath.stroke(rect)
69 | }
70 |
71 | cgContext.restoreGState()
72 | }
73 |
74 | func polygon(_ points:[NSPoint], stroke stroke_:ConvertibleToNSColor? = NSColor.black, lineWidth:CGFloat=1.0, fill fill_:ConvertibleToNSColor? = nil) {
75 |
76 | guard points.count >= 3 else {
77 | assertionFailure("at least 3 points are needed")
78 | return
79 | }
80 |
81 | cgContext.saveGState()
82 |
83 | let path = NSBezierPath()
84 |
85 | path.move(to: points[0])
86 |
87 | for i in 1...points.count-1 {
88 | path.line(to: points[i])
89 | }
90 |
91 | if let existingFillColor = fill_?.color {
92 | existingFillColor.setFill()
93 | path.fill()
94 | }
95 |
96 | path.close()
97 |
98 | if let existingStrokeColor = stroke_?.color {
99 | existingStrokeColor.setStroke()
100 | path.lineWidth = lineWidth
101 | path.stroke()
102 | }
103 |
104 | cgContext.restoreGState()
105 | }
106 |
107 | }
108 |
--------------------------------------------------------------------------------
/Shapefile/ShapefileBitmap.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BitmapCanvasShapefile.swift
3 | // Shapefile
4 | //
5 | // Created by nst on 25/03/16.
6 | // Copyright © 2016 Nicolas Seriot. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | class ShapefileBitmap : BitmapCanvas {
12 |
13 | var scale = 1.0
14 | var bbox = (x_min:0.0, y_min:0.0, x_max:0.0, y_max:0.0)
15 |
16 | convenience init?(maxWidth:Int, maxHeight:Int, bbox:(x_min:Double, y_min:Double, x_max:Double, y_max:Double), color:ConvertibleToNSColor?) {
17 |
18 | let bbox_w = bbox.x_max - bbox.x_min
19 | let bbox_h = bbox.y_max - bbox.y_min
20 |
21 | let isBboxWiderThanThanHeight = bbox_w > bbox_h
22 |
23 | let theScale = isBboxWiderThanThanHeight ? (Double(maxWidth) / bbox_w) : (Double(maxHeight) / bbox_h)
24 |
25 | print("-- scale: \(theScale)")
26 |
27 | let (w,h) = (Int(bbox_w * theScale), Int(bbox_h * theScale))
28 |
29 | self.init(w,h,color)
30 |
31 | self.scale = theScale
32 | self.bbox = bbox
33 | }
34 |
35 | func shape(_ shape:Shape, _ color:ConvertibleToNSColor?, lineWidth:CGFloat=1.0) {
36 |
37 | self.cgContext.saveGState()
38 |
39 | // shapefile coordinates start bottom left
40 | self.cgContext.translateBy(x: 0, y: CGFloat(self.height))
41 | self.cgContext.scaleBy(x: 1.0, y: -1.0)
42 |
43 | // scale and translate according to bbox
44 | self.cgContext.scaleBy(x: CGFloat(scale), y: CGFloat(scale))
45 | self.cgContext.translateBy(x: CGFloat(-self.bbox.x_min), y: CGFloat(-self.bbox.y_min))
46 |
47 | for points in shape.partPointsGenerator() {
48 | self.polygon(points, lineWidth:lineWidth / CGFloat(scale), fill:color)
49 | }
50 |
51 | self.cgContext.restoreGState()
52 | }
53 |
54 | func scaleVertical(_ rect:CGRect, startColor:ConvertibleToNSColor, stopColor:ConvertibleToNSColor, min:Int, max:Int) {
55 | // TODO: support horizontal scale
56 | // TODO: improve generalisation with more options related to graduations
57 |
58 | let c1 = startColor.color
59 | let c2 = stopColor.color
60 |
61 | let count = 2
62 | let locations : [CGFloat] = [ 1.0, 0.0 ]
63 | let components : [CGFloat] = [
64 | c1.redComponent, c1.greenComponent, c1.blueComponent, c1.alphaComponent, // start color
65 | c2.redComponent, c2.greenComponent, c2.blueComponent, c2.alphaComponent // end color
66 | ]
67 |
68 | guard let gradient = CGGradient(colorSpace: CGColorSpaceCreateDeviceRGB(), colorComponents: components, locations: locations, count: count) else { assertionFailure(); return }
69 |
70 | self.cgContext.saveGState()
71 | self.cgContext.addRect(rect)
72 | self.cgContext.clip()
73 | let startPoint = P(rect.origin.x,rect.origin.y)
74 | let endPoint = P(rect.origin.x+rect.size.width, rect.origin.y+rect.size.height)
75 | self.cgContext.drawLinearGradient (gradient, start: startPoint, end: endPoint, options: [])
76 | self.cgContext.restoreGState()
77 |
78 | let delta = max - min
79 | for value in min.. [String] {
13 |
14 | let regex = try NSRegularExpression(pattern: pattern, options: [])
15 | let matches = regex.matches(in: s, options: [], range: NSMakeRange(0, s.characters.count))
16 |
17 | var results : [String] = []
18 |
19 | for m in matches {
20 | for i in 1..> 16) & 0xff) / 255
45 | let g = CGFloat((self >> 08) & 0xff) / 255
46 | let b = CGFloat((self >> 00) & 0xff) / 255
47 |
48 | return NSColor(calibratedRed:r, green:g, blue:b, alpha:1.0)
49 | }
50 | }
51 |
52 | extension String : ConvertibleToNSColor {
53 |
54 | var color : NSColor {
55 |
56 | let scanner = Scanner(string: self)
57 |
58 | if scanner.scanString("#", into: nil) {
59 | var result : UInt32 = 0
60 | if scanner.scanHexInt32(&result) {
61 | return result.color
62 | } else {
63 | assertionFailure("cannot convert \(self) to hex color)")
64 | return NSColor.clear
65 | }
66 | }
67 |
68 | if let c = X11Colors.sharedInstance.colorList.color(withKey: self.lowercased()) {
69 | return c
70 | }
71 |
72 | assertionFailure("cannot convert \(self) into color)")
73 | return NSColor.clear
74 | }
75 | }
76 |
77 | extension NSColor {
78 |
79 | convenience init(_ r:Int, _ g:Int, _ b:Int, _ a:Int = 255) {
80 | self.init(
81 | calibratedRed: CGFloat(r)/255.0,
82 | green: CGFloat(g)/255.0,
83 | blue: CGFloat(b)/255.0,
84 | alpha: CGFloat(a)/255.0)
85 | }
86 |
87 | class var randomColor : NSColor {
88 | return C(Int(arc4random_uniform(256)), Int(arc4random_uniform(256)), Int(arc4random_uniform(256)))
89 | }
90 | }
91 |
92 | func C(_ r:Int, _ g:Int, _ b:Int, _ a:Int = 255) -> NSColor {
93 | return NSColor(r,g,b,a)
94 | }
95 |
96 | func C(_ r:CGFloat, _ g:CGFloat, _ b:CGFloat, _ a:CGFloat = 255.0) -> NSColor {
97 | return NSColor(calibratedRed: r, green: g, blue: b, alpha: a)
98 | }
99 |
100 | class X11Colors {
101 |
102 | static let sharedInstance = X11Colors(namePrettifier: { $0.lowercased() })
103 |
104 | var colorList = NSColorList(name: "X11")
105 |
106 | init(path:String = "/opt/X11/share/X11/rgb.txt", namePrettifier:@escaping (_ original:String) -> (String)) {
107 |
108 | let contents = try! String(contentsOfFile: path, encoding: String.Encoding.utf8)
109 |
110 | contents.enumerateLines { (line, stop) in
111 |
112 | let pattern = "\\s?+(\\d+?)\\s+(\\d+?)\\s+(\\d+?)\\s+(\\w+)$"
113 | let matches = try! NSRegularExpression.findAll(string: line, pattern: pattern)
114 | if matches.count != 4 { return } // ignore names with white spaces, they also appear in camel case
115 |
116 | let r = CGFloat(Int(matches[0])!)
117 | let g = CGFloat(Int(matches[1])!)
118 | let b = CGFloat(Int(matches[2])!)
119 |
120 | let name = matches[3]
121 |
122 | let prettyName = namePrettifier(name)
123 |
124 | let color = NSColor(calibratedRed: r/255.0, green: g/255.0, blue: b/255.0, alpha: 1.0)
125 | self.colorList.setColor(color, forKey: prettyName)
126 |
127 | //print("\(name) \t -> \t \(prettyName)")
128 | }
129 | }
130 |
131 | static func dump(_ inPath:String, outPath:String) -> Bool {
132 |
133 | let x11Colors = X11Colors(namePrettifier: {
134 | let name = ($0 as NSString)
135 | let firstCharacter = name.substring(to: 1)
136 | let restOfString = name.substring(with: NSMakeRange(1, name.lengthOfBytes(using: String.Encoding.utf8.rawValue)-1))
137 | return "\(firstCharacter.uppercased())\(restOfString)"
138 | })
139 |
140 | return x11Colors.colorList.write(toFile: outPath)
141 | }
142 | }
143 |
--------------------------------------------------------------------------------
/Shapefile/ShapefileView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ShapefileView.swift
3 | // Shapefile
4 | //
5 | // Created by nst on 26/03/16.
6 | // Copyright © 2016 Nicolas Seriot. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | class ShapefileView : CanvasView {
12 |
13 | var scale = 1.0
14 | var bbox = (x_min:0.0, y_min:0.0, x_max:0.0, y_max:0.0)
15 |
16 | required init?(coder: NSCoder) {
17 | super.init(coder: coder)
18 | }
19 |
20 | init?(maxWidth:Int, maxHeight:Int, bbox:(x_min:Double, y_min:Double, x_max:Double, y_max:Double), color:ConvertibleToNSColor?) {
21 |
22 | self.bbox = bbox
23 | let bbox_w = bbox.x_max - bbox.x_min
24 | let bbox_h = bbox.y_max - bbox.y_min
25 |
26 | let isBboxWiderThanThanHeight = bbox_w > bbox_h
27 |
28 | let maxWidth = 2000
29 | let maxHeight = 2000
30 |
31 | self.scale = isBboxWiderThanThanHeight ? (Double(maxWidth) / bbox_w) : (Double(maxHeight) / bbox_h)
32 |
33 | Swift.print("-- scale: \(scale)")
34 |
35 | let (w,h) = (bbox_w * scale, bbox_h * scale)
36 |
37 | super.init(frame: NSMakeRect(0, 0, CGFloat(w), CGFloat(h)))
38 | }
39 |
40 | override func draw(_ dirtyRect: NSRect) {
41 |
42 | super.draw(dirtyRect)
43 |
44 | let shapefileReader = try! ShapefileReader(path: "/Users/nst/Projects/ShapefileReader/data/g2g15.shp")
45 |
46 | let context = unsafeBitCast(NSGraphicsContext.current()!.graphicsPort, to: CGContext.self)
47 |
48 | context.saveGState()
49 |
50 | // makes coordinates start upper left
51 | context.translateBy(x: 0, y: CGFloat(self.bounds.height))
52 | context.scaleBy(x: 1.0, y: -1.0)
53 |
54 | "skyBlue".color.setFill()
55 | NSBezierPath.fill(dirtyRect)
56 |
57 | let zipForTownCode = zipForTownCodeDictionary()
58 |
59 | self.rectangle(R(10,10,665,40), stroke: "black", fill: "white")
60 |
61 | self.text("ZIP codes of the 2328 swiss towns, 2015", P(15,15), font:NSFont(name: "Helvetica", size: 36)!)
62 |
63 | self.text("Generated with ShapefileReader https://github.com/nst/ShapefileReader", P(10,self.bounds.height-35))
64 | self.text("Data: Federal Statistical Office (FSO), GEOSTAT: g2g15, g1k15", P(10,self.bounds.height-20))
65 |
66 | for (shape, record) in shapefileReader.shapeAndRecordGenerator() {
67 | //print(record)
68 | let n = record[0] as! Int
69 |
70 | var color = "black".color
71 | if let (zip, _) = zipForTownCode[n] {
72 | color = colorForZIP(zip)
73 | } else {
74 | Swift.print("-- cannot find zip for town \(record)")
75 | }
76 |
77 | self.shape(context, shape, color, lineWidth: 0.5)
78 | }
79 |
80 | // g1k15.shp // cantons
81 | let src = try! ShapefileReader(path: "/Users/nst/Projects/ShapefileReader/data/g1k15.shp")
82 |
83 | for shape in src.shp.shapeGenerator() {
84 | self.shape(context, shape, NSColor.clear, lineWidth: 1.5)
85 | }
86 |
87 | // ZIP labels
88 |
89 | drawZipLabel(context, 1000, P(265,841))
90 | drawZipLabel(context, 1100, P(166,865))
91 | drawZipLabel(context, 1200, P(122,1044))
92 | drawZipLabel(context, 1300, P(88,670))
93 | drawZipLabel(context, 1400, P(132,617))
94 | drawZipLabel(context, 1500, P(395,635))
95 | drawZipLabel(context, 1600, P(469,778))
96 | drawZipLabel(context, 1700, P(531,663))
97 | drawZipLabel(context, 1800, P(299,971))
98 | drawZipLabel(context, 1900, P(365,1162))
99 |
100 | drawZipLabel(context, 2000, P(285,414))
101 | drawZipLabel(context, 3000, P(640,535))
102 | drawZipLabel(context, 3700, P(676,789))
103 | drawZipLabel(context, 3800, P(918,745))
104 | drawZipLabel(context, 3900, P(939,1100))
105 |
106 | drawZipLabel(context, 4000, P(641,111))
107 | drawZipLabel(context, 5000, P(904,97))
108 | drawZipLabel(context, 6000, P(969,535))
109 | drawZipLabel(context, 6500, P(1484,964))
110 | drawZipLabel(context, 6600, P(1062,1024))
111 | drawZipLabel(context, 6700, P(1262,819))
112 | drawZipLabel(context, 6800, P(1410,1210))
113 | drawZipLabel(context, 6900, P(1405,1094))
114 |
115 | drawZipLabel(context, 7000, P(1752,513))
116 | drawZipLabel(context, 8000, P(1293,47))
117 | drawZipLabel(context, 9000, P(1611,286))
118 |
119 | context.restoreGState()
120 | }
121 |
122 | func shape(_ context:CGContext?, _ shape:Shape, _ color:ConvertibleToNSColor?, lineWidth:CGFloat=1.0) {
123 |
124 | context?.saveGState()
125 |
126 | // shapefile coordinates start bottom left
127 | context?.translateBy(x: 0, y: CGFloat(self.bounds.size.height))
128 | context?.scaleBy(x: 1.0, y: -1.0)
129 |
130 | // scale and translate according to bbox
131 | context?.scaleBy(x: CGFloat(scale), y: CGFloat(scale))
132 | context?.translateBy(x: CGFloat(-self.bbox.x_min), y: CGFloat(-self.bbox.y_min))
133 |
134 | for points in shape.partPointsGenerator() {
135 | self.polygon(points, lineWidth:lineWidth / CGFloat(scale), fill:color)
136 | }
137 |
138 | context?.restoreGState()
139 | }
140 |
141 | func drawZipLabel(_ context:CGContext?, _ zip:Int, _ p:CGPoint) {
142 | self.rectangle(R(p.x,p.y,75,32), stroke:"black", fill:colorForZIP(zip))
143 | self.text(String(zip), P(p.x+10,p.y+5), font:NSFont(name: "Courier", size: 24)!)
144 | }
145 | }
146 |
--------------------------------------------------------------------------------
/Shapefile/main.swift:
--------------------------------------------------------------------------------
1 | //
2 | // main.swift
3 | // Shapefile
4 | //
5 | // Created by nst on 11/03/16.
6 | // Copyright © 2016 Nicolas Seriot. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Cocoa
11 |
12 | func drawAltitudes() throws {
13 |
14 | let path = "/Users/nst/Projects/ShapefileReader/data/g2g15.dbf"
15 |
16 | assert(FileManager.default.fileExists(atPath: path), "update the path of the dbf file according to your project's location")
17 |
18 | let sr = try ShapefileReader(path:path)
19 |
20 | let b = ShapefileBitmap(maxWidth: 2000, maxHeight: 2000, bbox:sr.shp!.bbox, color:"SkyBlue")!
21 |
22 | b.rectangle(R(10,10,720,40), stroke: "black", fill: "white")
23 |
24 | b.setAllowsAntialiasing(true)
25 | b.text("Mean altitude of the 2328 swiss towns, 2015", P(15,15), font:NSFont(name: "Helvetica", size: 36)!)
26 |
27 | b.setAllowsAntialiasing(false)
28 | b.text("Generated with ShapefileReader https://github.com/nst/ShapefileReader", P(10,b.height-35))
29 | b.text("Data: Federal Statistical Office (FSO), GEOSTAT: g2g15", P(10,b.height-20))
30 |
31 | b.setAllowsAntialiasing(true)
32 |
33 | print("-- numberOfRecords:", sr.dbf!.numberOfRecords)
34 | print("-- numberOfShapes:", sr.shx!.numberOfShapes)
35 |
36 | assert(sr.dbf!.numberOfRecords == sr.shx!.numberOfShapes)
37 |
38 | let altitudes = try! sr.dbf!.recordGenerator().map{ $0[15] as! Int }
39 | let alt_min = altitudes.min()!
40 | let alt_max = altitudes.max()!
41 |
42 | /**/
43 |
44 | for (shape, record) in sr.shapeAndRecordGenerator() {
45 | let altitude = record[15] as! Int
46 |
47 | let factor = Double(altitude - alt_min) / Double(alt_max - alt_min)
48 | let color = NSColor(calibratedRed:CGFloat(factor), green:CGFloat(1.0-factor), blue:0.0, alpha:1.0)
49 |
50 | b.shape(shape, color, lineWidth: 0.5)
51 | }
52 |
53 | print("-- alt_max: \(alt_max)")
54 | print("-- alt_min: \(alt_min)")
55 |
56 | b.scaleVertical(R(10,70,30,500), startColor:"green", stopColor:"red", min:alt_min, max:alt_max)
57 |
58 | b.save("/tmp/switzerland_altitude.png", open: true)
59 | }
60 |
61 | func zipForTownCodeDictionary() -> [Int:(Int,String)] {
62 | // http://www.taed.ch/dl/plz_p1.txt
63 | let path = "/Users/nst/Projects/ShapefileReader/data/plz_p1.txt"
64 |
65 | let s = try! NSString(contentsOfFile: path, encoding: String.Encoding.utf8.rawValue)
66 |
67 | var d : [Int:(Int,String)] = [:]
68 |
69 | s.enumerateLines { (s, stopPtr) -> Void in
70 | let comps = s.components(separatedBy: "\t")
71 | let zip = Int(String(comps[2]))!
72 | let name = comps[5]
73 | let n = Int(String(comps[11]))!
74 |
75 | if let _ = d[n] { return }
76 |
77 | d[n] = (zip, name)
78 | }
79 |
80 | return d
81 | }
82 |
83 | func paletteForColorNumber(_ n:Int) -> (NSColor, NSColor, NSColor) {
84 | assert(0...3 ~= n)
85 |
86 | switch(n) {
87 | case 0:
88 | return ("LightPink".color, "PaleVioletRed".color, "Maroon".color)
89 | case 1:
90 | return ("SeaGreen4".color, "SeaGreen3".color, "SeaGreen1".color)
91 | case 2:
92 | return ("GoldenRod4".color, "GoldenRod3".color, "GoldenRod1".color)
93 | case 3:
94 | return ("SkyBlue4".color, "SkyBlue3".color, "SkyBlue1".color)
95 | default:
96 | assertionFailure()
97 | }
98 |
99 | return ("black".color, "black".color, "black".color)
100 | }
101 |
102 | func colorForZIP(_ zip:Int) -> NSColor {
103 |
104 | var color : NSColor
105 |
106 | let s = String(zip)
107 |
108 | switch((s as NSString).substring(to: 2)) {
109 | case "10", "12", "17", "19":
110 | color = paletteForColorNumber(0).0
111 | case "11", "14", "16":
112 | color = paletteForColorNumber(0).1
113 | case "13", "15", "18":
114 | color = paletteForColorNumber(0).2
115 | case "20", "23", "26", "29":
116 | color = paletteForColorNumber(1).0
117 | case "21", "24", "27":
118 | color = paletteForColorNumber(1).1
119 | case "22", "25", "28":
120 | color = paletteForColorNumber(1).2
121 | case "30", "33", "36", "39":
122 | color = paletteForColorNumber(2).0
123 | case "31", "34", "37":
124 | color = paletteForColorNumber(2).1
125 | case "32", "35", "38":
126 | color = paletteForColorNumber(2).2
127 | case "40", "43", "46", "49":
128 | color = paletteForColorNumber(0).0
129 | case "41", "44", "47":
130 | color = paletteForColorNumber(0).1
131 | case "42", "45", "48":
132 | color = paletteForColorNumber(0).2
133 | case "50", "53", "56", "59":
134 | color = paletteForColorNumber(2).0
135 | case "51", "54", "57":
136 | color = paletteForColorNumber(2).1
137 | case "52", "55", "58":
138 | color = paletteForColorNumber(2).2
139 | case "60", "63", "66", "69":
140 | color = paletteForColorNumber(1).0
141 | case "61", "64", "67":
142 | color = paletteForColorNumber(1).1
143 | case "62", "65", "68":
144 | color = paletteForColorNumber(1).2
145 | case "70", "73", "76", "79":
146 | color = paletteForColorNumber(2).0
147 | case "71", "74", "77":
148 | color = paletteForColorNumber(2).1
149 | case "72", "75", "78":
150 | color = paletteForColorNumber(2).2
151 | case "80", "83", "86", "89":
152 | color = paletteForColorNumber(0).0
153 | case "81", "84", "87":
154 | color = paletteForColorNumber(0).1
155 | case "82", "85", "88":
156 | color = paletteForColorNumber(0).2
157 | case "90", "93", "96", "99":
158 | color = paletteForColorNumber(1).0
159 | case "91", "94", "97":
160 | color = paletteForColorNumber(1).1
161 | case "92", "95", "98":
162 | color = paletteForColorNumber(1).2
163 | default:
164 | color = "black".color
165 | }
166 |
167 | let (r,g,b) = (color.redComponent, color.greenComponent, color.blueComponent)
168 |
169 | // let r2 : CGFloat = r + (0.8 - r/2.0) * factor*0.8
170 | // let g2 : CGFloat = g + (0.8 - g/2.0) * factor*0.8
171 | // let b2 : CGFloat = b + (0.8 - b/2.0) * factor*0.8
172 |
173 | // let r2 : CGFloat = r + (1.0 - r) * factor*0.85
174 | // let g2 : CGFloat = g + (1.0 - g) * factor*0.85
175 | // let b2 : CGFloat = b + (1.0 - b) * factor*0.85
176 |
177 | return NSColor(calibratedRed:r, green:g, blue:b, alpha:1.0)
178 | }
179 |
180 | func drawZipLabel(_ b:BitmapCanvas, _ zip:Int, _ p:CGPoint, _ name:String?=nil) {
181 | var s = String(zip)
182 | if let n = name {
183 | s += " \(n)"
184 | }
185 |
186 | let font = NSFont(name: "Courier", size: 18)!
187 |
188 | let textWidth = BitmapCanvas.textWidth(s as NSString, font:font)
189 |
190 | b.rectangle(R(p.x,p.y,10 + textWidth,26), stroke:"black", fill:colorForZIP(zip))
191 | b.text(s, P(p.x+5,p.y+5), font:font)
192 | }
193 |
194 | func printZipDistribution(_ zipForTownCode:[Int:(Int,String)]) {
195 | var d : [Int:Int] = [:]
196 |
197 | print("--", zipForTownCode[4284])
198 |
199 | for i in 1...9 {
200 | for j in 0...9 {
201 | let n = i*10 + j
202 | d[n] = 0
203 | }
204 | }
205 |
206 | for (_,(zip,_)) in zipForTownCode {
207 | let shortZip = zip / 100
208 | d[shortZip]? += 1
209 | }
210 |
211 | let a = d.sorted {$1.0 < $0.0}
212 |
213 | for t in a {
214 | if t.1 != 0 {
215 | print(t)
216 | }
217 | }
218 | }
219 |
220 | func drawZIPCodes() throws {
221 |
222 | let zipForTownCode = zipForTownCodeDictionary()
223 |
224 | printZipDistribution(zipForTownCode)
225 |
226 | // g2g15.shp // communes
227 | let sr = try ShapefileReader(path: "/Users/nst/Projects/ShapefileReader/data/g2g15.shp")
228 |
229 | let b = ShapefileBitmap(maxWidth: 2000, maxHeight: 2000, bbox:sr.shp!.bbox, color:"SkyBlue")!
230 |
231 | b.rectangle(R(10,10,665,40), stroke: "black", fill: "white")
232 |
233 | b.setAllowsAntialiasing(true)
234 | b.text("ZIP codes of the 2328 swiss towns, 2015", P(15,15), font:NSFont(name: "Helvetica", size: 36)!)
235 |
236 | b.setAllowsAntialiasing(false)
237 | b.text("Generated with ShapefileReader https://github.com/nst/ShapefileReader", P(10,b.height-35))
238 | b.text("Data: Federal Statistical Office (FSO), GEOSTAT: g2g15, g1k15", P(10,b.height-20))
239 |
240 | b.setAllowsAntialiasing(true)
241 |
242 | for (shape, record) in sr.shapeAndRecordGenerator() {
243 | let n = record[0] as! Int
244 |
245 | var color = "black".color
246 | if let (zip, _) = zipForTownCode[n] {
247 | //if zip != 1950 { continue }
248 |
249 | color = colorForZIP(zip)
250 | } else {
251 | print("-- cannot find zip for town \(record)")
252 | }
253 |
254 | b.shape(shape, color, lineWidth: 0.5)
255 | }
256 |
257 | // g1k15.shp // cantons
258 | let src = try ShapefileReader(path: "/Users/nst/Projects/ShapefileReader/data/g1k15.shp")
259 |
260 | for shape in src.shp.shapeGenerator() {
261 | b.shape(shape, NSColor.clear, lineWidth: 1.5)
262 | }
263 |
264 | // ZIP labels
265 |
266 | drawZipLabel(b, 1000, P(276,841), "Lausanne")
267 | drawZipLabel(b, 1100, P(166,865))
268 | drawZipLabel(b, 1200, P(122,1044), "Genève")
269 | drawZipLabel(b, 1300, P(88,670), "Éclépens")
270 | drawZipLabel(b, 1400, P(324,673), "Yverdon")
271 | drawZipLabel(b, 1500, P(395,635))
272 | drawZipLabel(b, 1600, P(469,778))
273 | drawZipLabel(b, 1700, P(531,663), "Fribourg")
274 | drawZipLabel(b, 1800, P(412,865), "Vevey")
275 | drawZipLabel(b, 1900, P(365,1162))
276 | drawZipLabel(b, 1950, P(632,1012), "Sion")
277 |
278 | drawZipLabel(b, 2000, P(279,460), "Neuchâtel")
279 |
280 | drawZipLabel(b, 3000, P(640,535), "Bern")
281 | drawZipLabel(b, 3700, P(666,726), "Spiez")
282 | drawZipLabel(b, 3800, P(850,727), "Interlaken")
283 | drawZipLabel(b, 3900, P(899,974), "Brig")
284 |
285 | drawZipLabel(b, 4000, P(641,111), "Basel")
286 |
287 | drawZipLabel(b, 5000, P(920,223), "Aarau")
288 |
289 | drawZipLabel(b, 6000, P(1009,503), "Luzern")
290 | drawZipLabel(b, 6400, P(1131,406), "Zug")
291 | drawZipLabel(b, 6500, P(1384,1008), "Bellinzona")
292 | drawZipLabel(b, 6600, P(1096,1031), "Locarno")
293 | drawZipLabel(b, 6700, P(1262,819))
294 | drawZipLabel(b, 6800, P(1410,1210))
295 | drawZipLabel(b, 6900, P(1379,1117), "Lugano")
296 |
297 | drawZipLabel(b, 7000, P(1582,595), "Chur")
298 | drawZipLabel(b, 7500, P(1703,721), "St. Moritz")
299 |
300 | drawZipLabel(b, 8000, P(1128,250), "Zürich")
301 | drawZipLabel(b, 8200, P(1167,47), "Schaffhausen")
302 | drawZipLabel(b, 8400, P(1219,185), "Winterthur")
303 | drawZipLabel(b, 8500, P(1295,144), "Frauenfeld")
304 |
305 | drawZipLabel(b, 9000, P(1504,234), "St. Gallen")
306 |
307 | b.save("/tmp/switzerland_zip.png", open: true)
308 | }
309 |
310 | func drawZIPCodesPDF() throws {
311 | let sr = try ShapefileReader(path: "/Users/nst/Projects/ShapefileReader/data/g2g15.shp")
312 | let view = ShapefileView(maxWidth: 2000, maxHeight: 2000, bbox:sr.shp!.bbox, color:"SkyBlue")!
313 | let pdfData = view.dataWithPDF(inside: view.frame)
314 | let path = "/tmp/switzerland_zip.pdf"
315 | let success = (try? pdfData.write(to: URL(fileURLWithPath: path), options: [.atomic])) != nil
316 | if success {
317 | NSWorkspace.shared().openFile(path)
318 | }
319 | }
320 |
321 | //drawAltitudes()
322 | try! drawZIPCodes()
323 | try! drawZIPCodesPDF()
324 |
--------------------------------------------------------------------------------
/vendor/BinUtils.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BinUtils.swift
3 | // BinUtils
4 | //
5 | // Created by Nicolas Seriot on 12/03/16.
6 | // Copyright © 2016 Nicolas Seriot. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | // MARK: protocol UnpackedType
12 |
13 | protocol Unpackable {}
14 |
15 | extension NSString: Unpackable {}
16 | extension Bool: Unpackable {}
17 | extension Int: Unpackable {}
18 | extension Double: Unpackable {}
19 |
20 | // MARK: protocol DataConvertible
21 |
22 | protocol DataConvertible {}
23 |
24 | extension DataConvertible {
25 |
26 | init?(data: Data) {
27 | guard data.count == MemoryLayout.size else { return nil }
28 | self = data.withUnsafeBytes { $0.pointee }
29 | }
30 |
31 | init?(bytes: [UInt8]) {
32 | let data = Data(bytes:bytes)
33 | self.init(data:data)
34 | }
35 |
36 | var data: Data {
37 | var value = self
38 | return Data(buffer: UnsafeBufferPointer(start: &value, count: 1))
39 | }
40 | }
41 |
42 | extension Bool : DataConvertible { }
43 |
44 | extension Int8 : DataConvertible { }
45 | extension Int16 : DataConvertible { }
46 | extension Int32 : DataConvertible { }
47 | extension Int64 : DataConvertible { }
48 |
49 | extension UInt8 : DataConvertible { }
50 | extension UInt16 : DataConvertible { }
51 | extension UInt32 : DataConvertible { }
52 | extension UInt64 : DataConvertible { }
53 |
54 | extension Float32 : DataConvertible { }
55 | extension Float64 : DataConvertible { }
56 |
57 | // MARK: String extension
58 |
59 | extension String {
60 | subscript (from:Int, to:Int) -> String {
61 | return (self as NSString).substring(with: NSMakeRange(from, to-from))
62 | }
63 | }
64 |
65 | // MARK: Data extension
66 |
67 | extension Data {
68 | var bytes : [UInt8] {
69 | return self.withUnsafeBytes {
70 | [UInt8](UnsafeBufferPointer(start: $0, count: self.count))
71 | }
72 | }
73 | }
74 |
75 | // MARK: functions
76 |
77 | func hexlify(_ data:Data) -> String {
78 |
79 | // similar to hexlify() in Python's binascii module
80 | // https://docs.python.org/2/library/binascii.html
81 |
82 | var s = String()
83 | var byte: UInt8 = 0
84 |
85 | for i in 0 ..< data.count {
86 | (data as NSData).getBytes(&byte, range: NSMakeRange(i, 1))
87 | s = s.appendingFormat("%02x", byte)
88 | }
89 |
90 | return s as String
91 | }
92 |
93 | func unhexlify(_ string:String) -> Data? {
94 |
95 | // similar to unhexlify() in Python's binascii module
96 | // https://docs.python.org/2/library/binascii.html
97 |
98 | let s = string.uppercased().replacingOccurrences(of: " ", with: "")
99 |
100 | let nonHexCharacterSet = CharacterSet(charactersIn: "0123456789ABCDEF").inverted
101 | if let range = s.rangeOfCharacter(from: nonHexCharacterSet) {
102 | print("-- found non hex character at range \(range)")
103 | return nil
104 | }
105 |
106 | var data = Data(capacity: s.characters.count / 2)
107 |
108 | for i in stride(from: 0, to:s.characters.count, by:2) {
109 | let byteString = s[i, i+2]
110 | let byte = UInt8(byteString.withCString { strtoul($0, nil, 16) })
111 | data.append([byte] as [UInt8], count: 1)
112 | }
113 |
114 | return data
115 | }
116 |
117 | func readIntegerType(_ type:T.Type, bytes:[UInt8], loc:inout Int) -> T {
118 | let size = MemoryLayout.size
119 | let sub = Array(bytes[loc..<(loc+size)])
120 | loc += size
121 | return T(bytes: sub)!
122 | }
123 |
124 | func readFloatingPointType(_ type:T.Type, bytes:[UInt8], loc:inout Int, isBigEndian:Bool) -> T {
125 | let size = MemoryLayout.size
126 | let sub = Array(bytes[loc..<(loc+size)])
127 | loc += size
128 | let sub_ = isBigEndian ? sub.reversed() : sub
129 | return T(bytes: sub_)!
130 | }
131 |
132 | func isBigEndianFromMandatoryByteOrderFirstCharacter(_ format:String) -> Bool {
133 |
134 | guard let firstChar = format.characters.first else { assertionFailure("empty format"); return false }
135 |
136 | let s = String(firstChar) as NSString
137 | let c = s.substring(to: 1)
138 |
139 | if c == "@" { assertionFailure("native size and alignment is unsupported") }
140 |
141 | if c == "=" || c == "<" { return false }
142 | if c == ">" || c == "!" { return true }
143 |
144 | assertionFailure("format '\(format)' first character must be among '=<>!'")
145 |
146 | return false
147 | }
148 |
149 | // akin to struct.calcsize(fmt)
150 | func numberOfBytesInFormat(_ format:String) -> Int {
151 |
152 | var numberOfBytes = 0
153 |
154 | var n = 0 // repeat counter
155 |
156 | var mutableFormat = format
157 |
158 | while mutableFormat.characters.count > 0 {
159 |
160 | let c = mutableFormat.remove(at: mutableFormat.startIndex)
161 |
162 | if let i = Int(String(c)) , 0...9 ~= i {
163 | if n > 0 { n *= 10 }
164 | n += i
165 | continue
166 | }
167 |
168 | if c == "s" {
169 | numberOfBytes += max(n,1)
170 | n = 0
171 | continue
172 | }
173 |
174 | for _ in 0..", "!", " ":
179 | ()
180 | case "c", "b", "B", "x", "?":
181 | numberOfBytes += 1
182 | case "h", "H":
183 | numberOfBytes += 2
184 | case "i", "l", "I", "L", "f":
185 | numberOfBytes += 4
186 | case "q", "Q", "d":
187 | numberOfBytes += 8
188 | case "P":
189 | numberOfBytes += MemoryLayout.size
190 | default:
191 | assertionFailure("-- unsupported format \(c)")
192 | }
193 | }
194 |
195 | n = 0
196 | }
197 |
198 | return numberOfBytes
199 | }
200 |
201 | func formatDoesMatchDataLength(_ format:String, data:Data) -> Bool {
202 | let sizeAccordingToFormat = numberOfBytesInFormat(format)
203 | let dataLength = data.count
204 | if sizeAccordingToFormat != dataLength {
205 | print("format \"\(format)\" expects \(sizeAccordingToFormat) bytes but data is \(dataLength) bytes")
206 | return false
207 | }
208 |
209 | return true
210 | }
211 |
212 | /*
213 | pack() and unpack() should behave as Python's struct module https://docs.python.org/2/library/struct.html BUT:
214 | - native size and alignment '@' is not supported
215 | - as a consequence, the byte order specifier character is mandatory and must be among "=<>!"
216 | - native byte order '=' assumes a little-endian system (eg. Intel x86)
217 | - Pascal strings 'p' and native pointers 'P' are not supported
218 | */
219 |
220 | enum BinUtilsError: Error {
221 | case formatDoesMatchDataLength(format:String, dataSize:Int)
222 | case unsupportedFormat(character:Character)
223 | }
224 |
225 | func pack(_ format:String, _ objects:[Any], _ stringEncoding:String.Encoding=String.Encoding.windowsCP1252) -> Data {
226 |
227 | var objectsQueue = objects
228 |
229 | var mutableFormat = format
230 |
231 | var mutableData = Data()
232 |
233 | var isBigEndian = false
234 |
235 | let firstCharacter = mutableFormat.remove(at: mutableFormat.startIndex)
236 |
237 | switch(firstCharacter) {
238 | case "<", "=":
239 | isBigEndian = false
240 | case ">", "!":
241 | isBigEndian = true
242 | case "@":
243 | assertionFailure("native size and alignment '@' is unsupported'")
244 | default:
245 | assertionFailure("unsupported format chacracter'")
246 | }
247 |
248 | var n = 0 // repeat counter
249 |
250 | while mutableFormat.characters.count > 0 {
251 |
252 | let c = mutableFormat.remove(at: mutableFormat.startIndex)
253 |
254 | if let i = Int(String(c)) , 0...9 ~= i {
255 | if n > 0 { n *= 10 }
256 | n += i
257 | continue
258 | }
259 |
260 | var o : Any = 0
261 |
262 | if c == "s" {
263 | o = objectsQueue.remove(at: 0)
264 |
265 | guard let stringData = (o as! String).data(using: .utf8) else { assertionFailure(); return Data() }
266 | var bytes = stringData.bytes
267 |
268 | let expectedSize = max(1, n)
269 |
270 | // pad ...
271 | while bytes.count < expectedSize { bytes.append(0x00) }
272 |
273 | // ... or trunk
274 | if bytes.count > expectedSize { bytes = Array(bytes[0.. [Unpackable] {
340 |
341 | assert(Int(OSHostByteOrder()) == OSLittleEndian, "\(#file) assumes little endian, but host is big endian")
342 |
343 | let isBigEndian = isBigEndianFromMandatoryByteOrderFirstCharacter(format)
344 |
345 | if formatDoesMatchDataLength(format, data: data) == false {
346 | throw BinUtilsError.formatDoesMatchDataLength(format:format, dataSize:data.count)
347 | }
348 |
349 | var a : [Unpackable] = []
350 |
351 | var loc = 0
352 |
353 | let bytes = data.bytes
354 |
355 | var n = 0 // repeat counter
356 |
357 | var mutableFormat = format
358 |
359 | mutableFormat.remove(at: mutableFormat.startIndex) // consume byte-order specifier
360 |
361 | while mutableFormat.characters.count > 0 {
362 |
363 | let c = mutableFormat.remove(at: mutableFormat.startIndex)
364 |
365 | if let i = Int(String(c)) , 0...9 ~= i {
366 | if n > 0 { n *= 10 }
367 | n += i
368 | continue
369 | }
370 |
371 | if c == "s" {
372 | let length = max(n,1)
373 | let sub = Array(bytes[loc.. CGFloat
14 | { return left * CGFloat(right) }
15 |
16 | func *(left:Int, right:CGFloat) -> CGFloat
17 | { return CGFloat(left) * right }
18 |
19 | func *(left:CGFloat, right:Double) -> CGFloat
20 | { return left * CGFloat(right) }
21 |
22 | func *(left:Double, right:CGFloat) -> CGFloat
23 | { return CGFloat(left) * right }
24 |
25 | infix operator + : AdditionPrecedence
26 |
27 | func +(left:CGFloat, right:Int) -> CGFloat
28 | { return left + CGFloat(right) }
29 |
30 | func +(left:Int, right:CGFloat) -> CGFloat
31 | { return CGFloat(left) + right }
32 |
33 | func +(left:CGFloat, right:Double) -> CGFloat
34 | { return left + CGFloat(right) }
35 |
36 | func +(left:Double, right:CGFloat) -> CGFloat
37 | { return CGFloat(left) + right }
38 |
39 | infix operator - : AdditionPrecedence
40 |
41 | func -(left:CGFloat, right:Int) -> CGFloat
42 | { return left - CGFloat(right) }
43 |
44 | func -(left:Int, right:CGFloat) -> CGFloat
45 | { return CGFloat(left) - right }
46 |
47 | func -(left:CGFloat, right:Double) -> CGFloat
48 | { return left - CGFloat(right) }
49 |
50 | func -(left:Double, right:CGFloat) -> CGFloat
51 | { return CGFloat(left) - right }
52 |
53 | //
54 |
55 | func P(_ x:CGFloat, _ y:CGFloat) -> NSPoint {
56 | return NSMakePoint(x, y)
57 | }
58 |
59 | func P(_ x:Int, _ y:Int) -> NSPoint {
60 | return NSMakePoint(CGFloat(x), CGFloat(y))
61 | }
62 |
63 | func RandomPoint(maxX:Int, maxY:Int) -> NSPoint {
64 | return P(CGFloat(arc4random_uniform((UInt32(maxX+1)))), CGFloat(arc4random_uniform((UInt32(maxY+1)))))
65 | }
66 |
67 | func R(_ x:CGFloat, _ y:CGFloat, _ w:CGFloat, _ h:CGFloat) -> NSRect {
68 | return NSMakeRect(x, y, w, h)
69 | }
70 |
71 | func R(_ x:Int, _ y:Int, _ w:Int, _ h:Int) -> NSRect {
72 | return NSMakeRect(CGFloat(x), CGFloat(y), CGFloat(w), CGFloat(h))
73 | }
74 |
75 | class BitmapCanvas {
76 |
77 | let bitmapImageRep : NSBitmapImageRep
78 | let context : NSGraphicsContext
79 |
80 | var cgContext : CGContext {
81 | return context.cgContext
82 | }
83 |
84 | var width : CGFloat {
85 | return bitmapImageRep.size.width
86 | }
87 |
88 | var height : CGFloat {
89 | return bitmapImageRep.size.height
90 | }
91 |
92 | func setAllowsAntialiasing(_ antialiasing : Bool) {
93 | cgContext.setAllowsAntialiasing(antialiasing)
94 | }
95 |
96 | init(_ width:Int, _ height:Int, _ background:ConvertibleToNSColor? = nil) {
97 |
98 | self.bitmapImageRep = NSBitmapImageRep(
99 | bitmapDataPlanes:nil,
100 | pixelsWide:width,
101 | pixelsHigh:height,
102 | bitsPerSample:8,
103 | samplesPerPixel:4,
104 | hasAlpha:true,
105 | isPlanar:false,
106 | colorSpaceName:NSDeviceRGBColorSpace,
107 | bytesPerRow:width*4,
108 | bitsPerPixel:32)!
109 |
110 | self.context = NSGraphicsContext(bitmapImageRep: bitmapImageRep)!
111 |
112 | NSGraphicsContext.setCurrent(context)
113 |
114 | setAllowsAntialiasing(false)
115 |
116 | if let b = background {
117 |
118 | let rect = NSMakeRect(0, 0, CGFloat(width), CGFloat(height))
119 |
120 | context.saveGraphicsState()
121 |
122 | b.color.setFill()
123 | NSBezierPath.fill(rect)
124 |
125 | context.restoreGraphicsState()
126 | }
127 |
128 | // makes coordinates start upper left
129 | cgContext.translateBy(x: 0, y: CGFloat(height))
130 | cgContext.scaleBy(x: 1.0, y: -1.0)
131 | }
132 |
133 | fileprivate func _colorIsEqual(_ p:NSPoint, _ pixelBuffer:UnsafePointer, _ rgba:(UInt8,UInt8,UInt8,UInt8)) -> Bool {
134 |
135 | let offset = 4 * ((Int(self.width) * Int(p.y) + Int(p.x)))
136 |
137 | let r = pixelBuffer[offset]
138 | let g = pixelBuffer[offset+1]
139 | let b = pixelBuffer[offset+2]
140 | let a = pixelBuffer[offset+3]
141 |
142 | if r != rgba.0 { return false }
143 | if g != rgba.1 { return false }
144 | if b != rgba.2 { return false }
145 | if a != rgba.3 { return false }
146 |
147 | return true
148 | }
149 |
150 | fileprivate func _color(_ p:NSPoint, pixelBuffer:UnsafePointer) -> NSColor {
151 |
152 | let offset = 4 * ((Int(self.width) * Int(p.y) + Int(p.x)))
153 |
154 | let r = pixelBuffer[offset]
155 | let g = pixelBuffer[offset+1]
156 | let b = pixelBuffer[offset+2]
157 | let a = pixelBuffer[offset+3]
158 |
159 | return NSColor(
160 | calibratedRed: CGFloat(Double(r)/255.0),
161 | green: CGFloat(Double(g)/255.0),
162 | blue: CGFloat(Double(b)/255.0),
163 | alpha: CGFloat(Double(a)/255.0))
164 | }
165 |
166 | func color(_ p:NSPoint) -> NSColor {
167 |
168 | guard let data = cgContext.data else { assertionFailure(); return NSColor.clear }
169 |
170 | let pixelBuffer = data.assumingMemoryBound(to: UInt8.self)
171 |
172 | return _color(p, pixelBuffer:pixelBuffer)
173 | }
174 |
175 | fileprivate func _setColor(_ p:NSPoint, pixelBuffer:UnsafeMutablePointer, normalizedColor:NSColor) {
176 | let offset = 4 * ((Int(self.width) * Int(p.y) + Int(p.x)))
177 |
178 | pixelBuffer[offset] = UInt8(normalizedColor.redComponent * 255.0)
179 | pixelBuffer[offset+1] = UInt8(normalizedColor.greenComponent * 255.0)
180 | pixelBuffer[offset+2] = UInt8(normalizedColor.blueComponent * 255.0)
181 | pixelBuffer[offset+3] = UInt8(normalizedColor.alphaComponent * 255.0)
182 | }
183 |
184 | func setColor(_ p:NSPoint, color color_:ConvertibleToNSColor) {
185 |
186 | let color = color_.color
187 |
188 | guard let normalizedColor = color.usingColorSpaceName(NSCalibratedRGBColorSpace) else {
189 | print("-- cannot normalize color \(color)")
190 | return
191 | }
192 |
193 | guard let data = cgContext.data else { assertionFailure(); return }
194 |
195 | let pixelBuffer = data.assumingMemoryBound(to: UInt8.self)
196 |
197 | _setColor(p, pixelBuffer:pixelBuffer, normalizedColor:normalizedColor)
198 | }
199 |
200 | subscript(x:Int, y:Int) -> ConvertibleToNSColor {
201 |
202 | get {
203 | let p = P(CGFloat(x),CGFloat(y))
204 | return color(p)
205 | }
206 |
207 | set {
208 | let p = P(CGFloat(x),CGFloat(y))
209 | setColor(p, color:newValue)
210 | }
211 | }
212 |
213 | func fill(_ p:NSPoint, color rawNewColor_:ConvertibleToNSColor) {
214 | // floodFillScanlineStack from http://lodev.org/cgtutor/floodfill.html
215 |
216 | let rawNewColor = rawNewColor_.color
217 |
218 | assert(p.x < width, "p.x \(p.x) out of range, must be < \(width)")
219 | assert(p.y < height, "p.y \(p.y) out of range, must be < \(height)")
220 |
221 | guard let data = cgContext.data else { assertionFailure(); return }
222 |
223 | let pixelBuffer = data.assumingMemoryBound(to: UInt8.self)
224 |
225 | guard let newColor = rawNewColor.usingColorSpaceName(NSCalibratedRGBColorSpace) else {
226 | print("-- cannot normalize color \(rawNewColor)")
227 | return
228 | }
229 |
230 | let oldColor = _color(p, pixelBuffer:pixelBuffer)
231 |
232 | if oldColor == newColor { return }
233 |
234 | // store rgba as [UInt8] to speed up comparisons
235 | var r : CGFloat = 0.0
236 | var g : CGFloat = 0.0
237 | var b : CGFloat = 0.0
238 | var a : CGFloat = 0.0
239 |
240 | oldColor.getRed(&r, green: &g, blue: &b, alpha: &a)
241 |
242 | let rgba = (UInt8(r*255),UInt8(g*255),UInt8(b*255),UInt8(a*255))
243 |
244 | var stack : [NSPoint] = [p]
245 |
246 | while let pp = stack.popLast() {
247 |
248 | var x1 = pp.x
249 |
250 | while(x1 >= 0 && _color(P(x1, pp.y), pixelBuffer:pixelBuffer) == oldColor) {
251 | x1 -= 1
252 | }
253 |
254 | x1 += 1
255 |
256 | var spanAbove = false
257 | var spanBelow = false
258 |
259 | while(x1 < width && _colorIsEqual(P(x1, pp.y), pixelBuffer, rgba )) {
260 |
261 | _setColor(P(x1, pp.y), pixelBuffer:pixelBuffer, normalizedColor:newColor)
262 |
263 | let north = P(x1, pp.y-1)
264 | let south = P(x1, pp.y+1)
265 |
266 | if spanAbove == false && pp.y > 0 && _colorIsEqual(north, pixelBuffer, rgba) {
267 | stack.append(north)
268 | spanAbove = true
269 | } else if spanAbove && pp.y > 0 && !_colorIsEqual(north, pixelBuffer, rgba) {
270 | spanAbove = false
271 | } else if spanBelow == false && pp.y < height - 1 && _colorIsEqual(south, pixelBuffer, rgba) {
272 | stack.append(south)
273 | spanBelow = true
274 | } else if spanBelow && pp.y < height - 1 && !_colorIsEqual(south, pixelBuffer, rgba) {
275 | spanBelow = false
276 | }
277 |
278 | x1 += 1
279 | }
280 | }
281 | }
282 |
283 | func line(_ p1:NSPoint, _ p2:NSPoint, _ color_:ConvertibleToNSColor? = NSColor.black) {
284 |
285 | let color = color_?.color
286 |
287 | context.saveGraphicsState()
288 |
289 | // align to the pixel grid
290 | cgContext.translateBy(x: 0.5, y: 0.5)
291 |
292 | if let existingColor = color {
293 | cgContext.setStrokeColor(existingColor.cgColor);
294 | }
295 |
296 | cgContext.setLineCap(.square)
297 | cgContext.move(to: CGPoint(x: p1.x, y: p1.y))
298 | cgContext.addLine(to: CGPoint(x: p2.x, y: p2.y))
299 | cgContext.strokePath()
300 |
301 | context.restoreGraphicsState()
302 | }
303 |
304 | func line(_ p1:NSPoint, length:CGFloat = 1.0, degreesCW:CGFloat = 0.0, _ color_:ConvertibleToNSColor? = NSColor.black) -> NSPoint {
305 | let color = color_?.color
306 | let radians = degreesToRadians(degreesCW)
307 | let p2 = P(p1.x + sin(radians) * length, p1.y - cos(radians) * length)
308 | self.line(p1, p2, color)
309 | return p2
310 | }
311 |
312 | func lineVertical(_ p1:NSPoint, height:CGFloat, _ color_:ConvertibleToNSColor? = nil) {
313 | let color = color_?.color
314 | let p2 = P(p1.x, p1.y + height - 1)
315 | self.line(p1, p2, color)
316 | }
317 |
318 | func lineHorizontal(_ p1:NSPoint, width:CGFloat, _ color_:ConvertibleToNSColor? = nil) {
319 | let color = color_?.color
320 | let p2 = P(p1.x + width - 1, p1.y)
321 | self.line(p1, p2, color)
322 | }
323 |
324 | func line(_ p1:NSPoint, deltaX:CGFloat, deltaY:CGFloat, _ color_:ConvertibleToNSColor? = nil) {
325 | let color = color_?.color
326 | let p2 = P(p1.x + deltaX, p1.y + deltaY)
327 | self.line(p1, p2, color)
328 | }
329 |
330 | func rectangle(_ rect:NSRect, stroke stroke_:ConvertibleToNSColor? = NSColor.black, fill fill_:ConvertibleToNSColor? = nil) {
331 |
332 | let stroke = stroke_?.color
333 | let fill = fill_?.color
334 |
335 | context.saveGraphicsState()
336 |
337 | // align to the pixel grid
338 | cgContext.translateBy(x: 0.5, y: 0.5)
339 |
340 | if let existingFillColor = fill {
341 | existingFillColor.setFill()
342 | NSBezierPath.fill(rect)
343 | }
344 |
345 | if let existingStrokeColor = stroke {
346 | existingStrokeColor.setStroke()
347 | NSBezierPath.stroke(rect)
348 | }
349 |
350 | context.restoreGraphicsState()
351 | }
352 |
353 | func polygon(_ points:[NSPoint], stroke stroke_:ConvertibleToNSColor? = NSColor.black, lineWidth:CGFloat=1.0, fill fill_:ConvertibleToNSColor? = nil) {
354 |
355 | guard points.count >= 3 else {
356 | assertionFailure("at least 3 points are needed")
357 | return
358 | }
359 |
360 | context.saveGraphicsState()
361 |
362 | let path = NSBezierPath()
363 |
364 | path.move(to: points[0])
365 |
366 | for i in 1...points.count-1 {
367 | path.line(to: points[i])
368 | }
369 |
370 | if let existingFillColor = fill_?.color {
371 | existingFillColor.setFill()
372 | path.fill()
373 | }
374 |
375 | path.close()
376 |
377 | if let existingStrokeColor = stroke_?.color {
378 | existingStrokeColor.setStroke()
379 | path.lineWidth = lineWidth
380 | path.stroke()
381 | }
382 |
383 | context.restoreGraphicsState()
384 | }
385 |
386 | func ellipse(_ rect:NSRect, stroke stroke_:ConvertibleToNSColor? = NSColor.black, fill fill_:ConvertibleToNSColor? = nil) {
387 |
388 | let strokeColor = stroke_?.color
389 | let fillColor = fill_?.color
390 |
391 | context.saveGraphicsState()
392 |
393 | // align to the pixel grid
394 | cgContext.translateBy(x: 0.5, y: 0.5)
395 |
396 | // fill
397 | if let existingFillColor = fillColor {
398 | existingFillColor.setFill()
399 |
400 | // reduce fillRect so that is doesn't cross the stoke
401 | let fillRect = R(rect.origin.x+1, rect.origin.y+1, rect.size.width-2, rect.size.height-2)
402 | cgContext.fillEllipse(in: fillRect)
403 | }
404 |
405 | // stroke
406 | if let existingStrokeColor = strokeColor { existingStrokeColor.setStroke() }
407 | cgContext.strokeEllipse(in: rect)
408 |
409 | context.restoreGraphicsState()
410 | }
411 |
412 | fileprivate func degreesToRadians(_ x:CGFloat) -> CGFloat {
413 | return (M_PI * x / 180.0)
414 | }
415 |
416 | func save(_ path:String, open:Bool=false) {
417 | guard let data = bitmapImageRep.representation(using: .PNG, properties: [:]) else {
418 | print("\(#file) \(#function) cannot get PNG data from bitmap")
419 | return
420 | }
421 |
422 | do {
423 | try data.write(to: URL(fileURLWithPath: path), options: [])
424 | if open {
425 | NSWorkspace.shared().openFile(path)
426 | }
427 | } catch let e {
428 | print(e)
429 | }
430 | }
431 |
432 | static func textWidth(_ text:NSString, font:NSFont) -> CGFloat {
433 | let maxSize : CGSize = CGSize(width: CGFloat.greatestFiniteMagnitude, height: font.pointSize)
434 | let textRect : CGRect = text.boundingRect(
435 | with: maxSize,
436 | options: NSStringDrawingOptions.usesLineFragmentOrigin,
437 | attributes: [NSFontAttributeName: font],
438 | context: nil)
439 | return textRect.size.width
440 | }
441 |
442 | func image(fromPath path:String, _ p:NSPoint) {
443 |
444 | guard let data = try? Data(contentsOf: URL(fileURLWithPath: path)) else {
445 | print("\(#file) \(#function) cannot read data at \(path)");
446 | return
447 | }
448 |
449 | guard let imgRep = NSBitmapImageRep(data: data) else {
450 | print("\(#file) \(#function) cannot create bitmap image rep from data at \(path)");
451 | return
452 | }
453 |
454 | guard let cgImage = imgRep.cgImage else {
455 | print("\(#file) \(#function) cannot get cgImage out of imageRep from \(path)");
456 | return
457 | }
458 |
459 | context.saveGraphicsState()
460 |
461 | cgContext.scaleBy(x: 1.0, y: -1.0)
462 | cgContext.translateBy(x: 0.0, y: -2.0 * p.y - imgRep.pixelsHigh)
463 |
464 | let rect = NSMakeRect(p.x, p.y, CGFloat(imgRep.pixelsWide), CGFloat(imgRep.pixelsHigh))
465 |
466 | cgContext.draw(cgImage, in: rect)
467 |
468 | context.restoreGraphicsState()
469 | }
470 |
471 | func text(_ text:String, _ p:NSPoint, rotationRadians:CGFloat?, font : NSFont = NSFont(name: "Monaco", size: 10)!, color color_ : ConvertibleToNSColor = NSColor.black) {
472 |
473 | let color = color_.color
474 |
475 | let attr = [
476 | NSFontAttributeName:font,
477 | NSForegroundColorAttributeName:color
478 | ]
479 |
480 | context.saveGraphicsState()
481 |
482 | if let radians = rotationRadians {
483 | cgContext.translateBy(x: p.x, y: p.y);
484 | cgContext.rotate(by: radians)
485 | cgContext.translateBy(x: -p.x, y: -p.y);
486 | }
487 |
488 | cgContext.scaleBy(x: 1.0, y: -1.0)
489 | cgContext.translateBy(x: 0.0, y: -2.0 * p.y - font.pointSize)
490 |
491 | text.draw(at: p, withAttributes: attr)
492 |
493 | context.restoreGraphicsState()
494 | }
495 |
496 | func text(_ text:String, _ p:NSPoint, rotationDegrees degrees:CGFloat = 0.0, font : NSFont = NSFont(name: "Monaco", size: 10)!, color : ConvertibleToNSColor = NSColor.black) {
497 | self.text(text, p, rotationRadians: degreesToRadians(degrees), font: font, color: color)
498 | }
499 | }
500 |
--------------------------------------------------------------------------------
/Shapefile/Shapefile.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Shapefile.swift
3 | // Unpack
4 | //
5 | // Created by nst on 12/03/16.
6 | // Copyright © 2016 Nicolas Seriot. All rights reserved.
7 | //
8 |
9 | // References:
10 | // https://www.esri.com/library/whitepapers/pdfs/shapefile.pdf
11 | // https://raw.githubusercontent.com/GeospatialPython/pyshp/master/shapefile.py
12 |
13 | import Foundation
14 | import CoreGraphics
15 | fileprivate func < (lhs: T?, rhs: T?) -> Bool {
16 | switch (lhs, rhs) {
17 | case let (l?, r?):
18 | return l < r
19 | case (nil, _?):
20 | return true
21 | default:
22 | return false
23 | }
24 | }
25 |
26 |
27 | typealias MapPoint = CGPoint
28 |
29 | enum ShapeType : Int {
30 | case nullShape = 0
31 | case point = 1
32 | case polyLine = 3
33 | case polygon = 5
34 | case multipoint = 8
35 | case pointZ = 11
36 | case polylineZ = 13
37 | case polygonZ = 15
38 | case multipointZ = 18
39 | case pointM = 21
40 | case polylineM = 23
41 | case polygonM = 25
42 | case multipointM = 28
43 | case multipatch = 31
44 |
45 | var hasBoundingBox : Bool {
46 | return [3,5,8,13,15,18,23,25,28,31].contains(self.rawValue)
47 | }
48 |
49 | var hasParts : Bool {
50 | return [3,5,13,15,23,25,31].contains(self.rawValue)
51 | }
52 |
53 | var hasPoints : Bool {
54 | return [3,5,8,13,15,23,25,31].contains(self.rawValue)
55 | }
56 |
57 | var hasZValues : Bool {
58 | return [13,15,18,31].contains(self.rawValue)
59 | }
60 |
61 | var hasMValues : Bool {
62 | return [13,15,18,23,25,28,31].contains(self.rawValue)
63 | }
64 |
65 | var hasSinglePoint : Bool {
66 | return [1,11,21].contains(self.rawValue)
67 | }
68 |
69 | var hasSingleZ : Bool {
70 | return [11].contains(self.rawValue)
71 | }
72 |
73 | var hasSingleM : Bool {
74 | return [11,21].contains(self.rawValue)
75 | }
76 | }
77 |
78 | class Shape {
79 | init(type:ShapeType = .nullShape) {
80 | self.shapeType = type
81 | }
82 |
83 | var shapeType : ShapeType
84 | var points : [MapPoint] = []
85 | var bbox : (x_min:Double, y_min:Double, x_max:Double, y_max:Double) = (0.0,0.0,0.0,0.0)
86 | var parts : [Int] = []
87 | var partTypes : [Int] = []
88 | var z : Double = 0.0
89 | var m : [Double?] = []
90 |
91 | func partPointsGenerator() -> AnyIterator<[MapPoint]> {
92 |
93 | var indices = Array(self.parts)
94 | indices.append(self.points.count-1)
95 |
96 | var i = 0
97 |
98 | return AnyIterator {
99 | if self.shapeType.hasParts == false { return nil }
100 |
101 | if i == indices.count - 1 { return nil }
102 |
103 | let partPoints = Array(self.points[indices[i].. DBFRecord {
181 |
182 | guard let f = self.fileHandle else {
183 | print("dbf file is missing")
184 | return []
185 | }
186 |
187 | f.seek(toFileOffset: offset)
188 |
189 | guard let recordContents = try! unpack(self.recordFormat, f.readData(ofLength: self.recordLengthFromHeader)) as? [NSString] else {
190 | print("bad record contents")
191 | return []
192 | }
193 |
194 | let isDeletedRecord = recordContents[0] != " "
195 | if isDeletedRecord { return [] }
196 |
197 | assert(self.fields.count == recordContents.count)
198 |
199 | var record : DBFRecord = []
200 |
201 | for (fields, value) in Array(zip(self.fields, recordContents)) {
202 |
203 | let name = fields[0] as! String
204 | let type = fields[1] as! String
205 | //let size = fields[2] as! Int
206 | let deci = fields[3] as! Int == 1
207 |
208 | if name == "DeletionFlag" { continue }
209 |
210 | let trimmedValue = value.trimmingCharacters(in: CharacterSet.whitespaces)
211 |
212 | if trimmedValue.characters.count == 0 {
213 | record.append("")
214 | continue
215 | }
216 |
217 | var v : Any = ""
218 |
219 | switch type {
220 | case "N": // Numeric, Number stored as a string, right justified, and padded with blanks to the width of the field.
221 | if trimmedValue == "" {
222 | v = trimmedValue
223 | } else if deci || trimmedValue.contains(".") {
224 | v = Double(trimmedValue)!
225 | } else {
226 | v = Int(trimmedValue)!
227 | }
228 | case "F": // Float - since dBASE IV 2.0
229 | v = Double(trimmedValue)!
230 | case "D": // Date, 8 bytes - date stored as a string in the format YYYYMMDD.
231 | v = trimmedValue
232 | case "C": // Character, All OEM code page characters - padded with blanks to the width of the field.
233 | v = trimmedValue
234 | case "L": // Logical, 1 byte - initialized to 0x20 (space) otherwise T or F. ? Y y N n T t F f (? when not initialized).
235 | v = ["T","t","Y","y"].contains(trimmedValue)
236 | case "M": // Memo, a string, 10 digits (bytes) representing a .DBT block number. The number is stored as a string, right justified and padded with blanks. All OEM code page characters (stored internally as 10 digits representing a .DBT block number).
237 | v = trimmedValue
238 | default:
239 | assertionFailure("unknown field type: \(type)")
240 | v = trimmedValue
241 | }
242 |
243 | record.append(v)
244 | }
245 |
246 | return record
247 | }
248 |
249 | subscript(i:Int) -> DBFRecord {
250 | return try! recordAtIndex(i)
251 | }
252 |
253 | func recordAtIndex(_ i:Int = 0) throws -> DBFRecord {
254 |
255 | guard let f = self.fileHandle else {
256 | print("no dbf")
257 | return []
258 | }
259 |
260 | f.seek(toFileOffset: 0)
261 | assert(headerLength != 0)
262 | let offset = headerLength + (i * recordLengthFromHeader)
263 | return try self.recordAtOffset(UInt64(offset))
264 | }
265 |
266 | func recordGenerator() throws -> AnyIterator {
267 |
268 | guard let n = self.numberOfRecords else {
269 | return AnyIterator {
270 | print("-- unknown number of records")
271 | return nil
272 | }
273 | }
274 |
275 | var i = 0
276 |
277 | return AnyIterator {
278 | if i >= n { return nil}
279 | let rec = try! self.recordAtIndex(i)
280 | i += 1
281 | return rec
282 | }
283 | }
284 |
285 | func allRecords() throws -> [DBFRecord] {
286 |
287 | var records : [DBFRecord] = []
288 |
289 | let generator = try self.recordGenerator()
290 |
291 | while let r = generator.next() {
292 | records.append(r)
293 | }
294 |
295 | return records
296 | }
297 |
298 | fileprivate func buildDBFRecordFormat() -> String {
299 | let a = self.fields.filter({ $0[2] is Int }).map({ $0[2] })
300 | let sizes = a as! [Int]
301 | let totalSize = sizes.reduce(0, +)
302 | let format = "<" + sizes.map( { String($0) + "s" } ).joined(separator: "")
303 |
304 | if totalSize != recordLengthFromHeader {
305 | print("-- error: record size declated in header \(recordLengthFromHeader) != record size declared in fields format \(totalSize)")
306 | recordLengthFromHeader = totalSize
307 | }
308 |
309 | return format
310 | }
311 | }
312 |
313 | class SHPReader {
314 |
315 | var fileHandle : FileHandle!
316 | var shapeType : ShapeType = .nullShape
317 | var bbox : (x_min:Double, y_min:Double, x_max:Double, y_max:Double) = (0.0,0.0,0.0,0.0) // Xmin, Ymin, Xmax, Ymax
318 | var elevation : (z_min:Double, z_max:Double) = (0.0, 0.0)
319 | var measure : (m_min:Double, m_max:Double) = (0.0, 0.0)
320 | var shpLength : UInt64 = 0
321 |
322 | init(path:String) throws {
323 | self.fileHandle = try FileHandle(forReadingFrom: URL(fileURLWithPath: path))
324 | try self.readHeader()
325 | }
326 |
327 | deinit {
328 | self.fileHandle.closeFile()
329 | }
330 |
331 | fileprivate func readHeader() throws {
332 |
333 | let f : FileHandle = self.fileHandle
334 |
335 | f.seek(toFileOffset: 24)
336 |
337 | let l = try unpack(">i", f.readData(ofLength: 4))
338 | self.shpLength = UInt64((l[0] as! Int) * 2)
339 |
340 | let a = try unpack(" use the actual one")
362 | self.shpLength = length
363 | }
364 | }
365 |
366 | func shapeAtOffset(_ offset:UInt64) throws -> (next:UInt64, shape:Shape)? {
367 |
368 | if offset == shpLength { return nil }
369 | assert(offset < shpLength, "trying to read shape at offset \(offset), but shpLength is only \(shpLength)")
370 |
371 | let record = Shape()
372 | var nParts : Int = 0
373 | var nPoints : Int = 0
374 |
375 | let f : FileHandle = self.fileHandle
376 |
377 | f.seek(toFileOffset: offset)
378 |
379 | let l = try unpack(">2i", f.readData(ofLength: 8))
380 | //let recNum = l[0] as! Int
381 | let recLength = l[1] as! Int
382 |
383 | let next = f.offsetInFile + UInt64((2 * recLength))
384 |
385 | let shapeTypeInt = try unpack(" 0 {
403 | record.parts = try unpack("<\(nParts)i", f.readData(ofLength: nParts * 4)).map({ $0 as! Int })
404 | }
405 |
406 | if shapeType == .multipatch {
407 | record.partTypes = try unpack("<\(nParts)i", f.readData(ofLength: nParts * 4)).map({ $0 as! Int })
408 | }
409 |
410 | var recPoints : [MapPoint] = []
411 | for _ in 0.. AnyIterator {
462 |
463 | var nextIndex : UInt64 = 100
464 |
465 | return AnyIterator {
466 | if let (next, shape) = try! self.shapeAtOffset(nextIndex) {
467 | nextIndex = next
468 | return shape
469 | }
470 | return nil
471 | }
472 | }
473 |
474 | func allShapes() -> [Shape] {
475 |
476 | var shapes : [Shape] = []
477 |
478 | let generator = self.shapeGenerator()
479 |
480 | while let s = generator.next() {
481 | shapes.append(s)
482 | }
483 |
484 | return shapes
485 | }
486 | }
487 |
488 | class SHXReader {
489 | /*
490 | The shapefile index contains the same 100-byte header as the .shp file, followed by any number of 8-byte fixed-length records which consist of the following two fields:
491 | Bytes Type Endianness Usage
492 | 0–3 int32 big Record offset (in 16-bit words)
493 | 4–7 int32 big Record length (in 16-bit words)
494 | https://en.wikipedia.org/wiki/Shapefile
495 | */
496 |
497 | var fileHandle : FileHandle!
498 | var shapeOffsets : [Int] = []
499 |
500 | var numberOfShapes : Int {
501 | return shapeOffsets.count
502 | }
503 |
504 | init(path:String) throws {
505 | self.fileHandle = try FileHandle(forReadingFrom: URL(fileURLWithPath: path))
506 | self.shapeOffsets = try self.readOffsets()
507 | }
508 |
509 | deinit {
510 | self.fileHandle?.closeFile()
511 | }
512 |
513 | fileprivate func readOffsets() throws -> [Int] {
514 |
515 | guard let f = self.fileHandle else {
516 | print("no shx")
517 | return []
518 | }
519 |
520 | // read number of records
521 | f.seek(toFileOffset: 24)
522 | let a = try unpack(">i", f.readData(ofLength: 4))
523 | let halfLength = a[0] as! Int
524 | let shxRecordLength = (halfLength * 2) - 100
525 | var numRecords = shxRecordLength / 8
526 |
527 | // measure number of records
528 | f.seekToEndOfFile()
529 | let eof = f.offsetInFile
530 | let lengthWithoutHeaders = eof - 100
531 | let numRecordsMeasured = Int(lengthWithoutHeaders / 8)
532 |
533 | // pick measured number of records if different
534 | if numRecords != numRecordsMeasured {
535 | print("-- numRecords \(numRecords) != numRecordsMeasured \(numRecordsMeasured) -> use numRecordsMeasured")
536 | numRecords = numRecordsMeasured
537 | }
538 |
539 | var offsets : [Int] = []
540 |
541 | // read the offsets
542 | for r in 0..i", f.readData(ofLength: 4))
546 | let i = b[0] as! Int
547 | offsets.append(i * 2)
548 | }
549 |
550 | return offsets
551 | }
552 |
553 | func shapeOffsetAtIndex(_ i:Int) -> Int? {
554 | return i < self.shapeOffsets.count ? self.shapeOffsets[i] : nil
555 | }
556 | }
557 |
558 | class ShapefileReader {
559 |
560 | var shp : SHPReader!
561 | var dbf : DBFReader? = nil
562 | var shx : SHXReader? = nil
563 |
564 | var shapeName : String
565 |
566 | init(path:String) throws {
567 |
568 | self.shapeName = (path as NSString).deletingPathExtension
569 |
570 | self.shp = try SHPReader(path: "\(shapeName).shp")
571 | self.dbf = try DBFReader(path: "\(shapeName).dbf")
572 | self.shx = try SHXReader(path: "\(shapeName).shx")
573 | }
574 |
575 | subscript(i:Int) -> Shape? {
576 | guard let shx = self.shx else {
577 | return nil
578 | }
579 |
580 | guard let offset = shx.shapeOffsetAtIndex(i) else { return nil }
581 |
582 | do {
583 | if let (_, shape) = try self.shp.shapeAtOffset(UInt64(offset)) {
584 | return shape
585 | }
586 | } catch {
587 | }
588 |
589 | return nil
590 | }
591 |
592 | func shapeAndRecordGenerator() -> AnyIterator<(Shape, DBFReader.DBFRecord)> {
593 |
594 | var i = 0
595 |
596 | return AnyIterator {
597 | guard let s = self[i] else { return nil }
598 | guard let r = self.dbf?[i] else { return nil }
599 | i += 1
600 | return (s, r)
601 | }
602 | }
603 |
604 | }
605 |
--------------------------------------------------------------------------------
/Shapefile.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 0344B4E21C9EFB02001DCDAB /* BinUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0344B4DF1C9EFB02001DCDAB /* BinUtils.swift */; };
11 | 0344B4E31C9EFB02001DCDAB /* BinUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0344B4DF1C9EFB02001DCDAB /* BinUtils.swift */; };
12 | 0344B4E41C9EFB02001DCDAB /* BitmapCanvas.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0344B4E01C9EFB02001DCDAB /* BitmapCanvas.swift */; };
13 | 0344B4E51C9EFB02001DCDAB /* BitmapCanvas.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0344B4E01C9EFB02001DCDAB /* BitmapCanvas.swift */; };
14 | 0344B4E61C9EFB02001DCDAB /* X11Colors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0344B4E11C9EFB02001DCDAB /* X11Colors.swift */; };
15 | 0344B4E71C9EFB02001DCDAB /* X11Colors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0344B4E11C9EFB02001DCDAB /* X11Colors.swift */; };
16 | 035D0FF21C9E1EA200BC89C8 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 035D0FF11C9E1EA200BC89C8 /* main.swift */; };
17 | 035D0FFF1C9E1EB200BC89C8 /* Shapefile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 035D0FFE1C9E1EB200BC89C8 /* Shapefile.swift */; };
18 | 035D10071C9E1FB200BC89C8 /* ShapefileTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 035D10061C9E1FB200BC89C8 /* ShapefileTests.swift */; };
19 | 035D10141C9E1FFA00BC89C8 /* Kantone.dbf in Resources */ = {isa = PBXBuildFile; fileRef = 035D100D1C9E1FFA00BC89C8 /* Kantone.dbf */; };
20 | 035D10151C9E1FFA00BC89C8 /* Kantone.prj in Resources */ = {isa = PBXBuildFile; fileRef = 035D100E1C9E1FFA00BC89C8 /* Kantone.prj */; };
21 | 035D10161C9E1FFA00BC89C8 /* Kantone.sbn in Resources */ = {isa = PBXBuildFile; fileRef = 035D100F1C9E1FFA00BC89C8 /* Kantone.sbn */; };
22 | 035D10171C9E1FFA00BC89C8 /* Kantone.sbx in Resources */ = {isa = PBXBuildFile; fileRef = 035D10101C9E1FFA00BC89C8 /* Kantone.sbx */; };
23 | 035D10181C9E1FFA00BC89C8 /* Kantone.shp in Resources */ = {isa = PBXBuildFile; fileRef = 035D10111C9E1FFA00BC89C8 /* Kantone.shp */; };
24 | 035D10191C9E1FFA00BC89C8 /* Kantone.shp.xml in Resources */ = {isa = PBXBuildFile; fileRef = 035D10121C9E1FFA00BC89C8 /* Kantone.shp.xml */; };
25 | 035D101A1C9E1FFA00BC89C8 /* Kantone.shx in Resources */ = {isa = PBXBuildFile; fileRef = 035D10131C9E1FFA00BC89C8 /* Kantone.shx */; };
26 | 035D101B1C9E208F00BC89C8 /* Shapefile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 035D0FFE1C9E1EB200BC89C8 /* Shapefile.swift */; };
27 | 03B7088A1CA5F08700F3B3A0 /* CanvasView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03B708891CA5F08700F3B3A0 /* CanvasView.swift */; };
28 | 03B7088C1CA5F6D200F3B3A0 /* ShapefileBitmap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03B7088B1CA5F6D200F3B3A0 /* ShapefileBitmap.swift */; };
29 | 03B708931CA73C2400F3B3A0 /* ShapefileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03B708921CA73C2400F3B3A0 /* ShapefileView.swift */; };
30 | /* End PBXBuildFile section */
31 |
32 | /* Begin PBXCopyFilesBuildPhase section */
33 | 035D0FEC1C9E1EA200BC89C8 /* CopyFiles */ = {
34 | isa = PBXCopyFilesBuildPhase;
35 | buildActionMask = 2147483647;
36 | dstPath = /usr/share/man/man1/;
37 | dstSubfolderSpec = 0;
38 | files = (
39 | );
40 | runOnlyForDeploymentPostprocessing = 1;
41 | };
42 | /* End PBXCopyFilesBuildPhase section */
43 |
44 | /* Begin PBXFileReference section */
45 | 032E45E71CA3E07E006C4622 /* g1k15.dbf */ = {isa = PBXFileReference; lastKnownFileType = file; path = g1k15.dbf; sourceTree = ""; };
46 | 032E45E81CA3E07E006C4622 /* g1k15.shp */ = {isa = PBXFileReference; lastKnownFileType = file; path = g1k15.shp; sourceTree = ""; };
47 | 032E45E91CA3E07E006C4622 /* g1k15.shx */ = {isa = PBXFileReference; lastKnownFileType = file; path = g1k15.shx; sourceTree = ""; };
48 | 032E45EA1CA3E0CC006C4622 /* g2g15.shx */ = {isa = PBXFileReference; lastKnownFileType = file; path = g2g15.shx; sourceTree = ""; };
49 | 032E45EB1CA3E0CC006C4622 /* g2g15.shp */ = {isa = PBXFileReference; lastKnownFileType = file; path = g2g15.shp; sourceTree = ""; };
50 | 032E45EC1CA3E0CC006C4622 /* g2g15.dbf */ = {isa = PBXFileReference; lastKnownFileType = file; path = g2g15.dbf; sourceTree = ""; };
51 | 032E45ED1CA3E12A006C4622 /* Conditions_d'exploitation-Copyright_OFS_GEOSTAT.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; path = "Conditions_d'exploitation-Copyright_OFS_GEOSTAT.pdf"; sourceTree = ""; };
52 | 0344B4DD1C9EEAFF001DCDAB /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; };
53 | 0344B4DF1C9EFB02001DCDAB /* BinUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BinUtils.swift; sourceTree = ""; };
54 | 0344B4E01C9EFB02001DCDAB /* BitmapCanvas.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BitmapCanvas.swift; sourceTree = ""; };
55 | 0344B4E11C9EFB02001DCDAB /* X11Colors.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = X11Colors.swift; sourceTree = ""; };
56 | 035D0FEE1C9E1EA200BC89C8 /* Shapefile */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = Shapefile; sourceTree = BUILT_PRODUCTS_DIR; };
57 | 035D0FF11C9E1EA200BC89C8 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; };
58 | 035D0FFE1C9E1EB200BC89C8 /* Shapefile.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Shapefile.swift; sourceTree = ""; };
59 | 035D10041C9E1FB200BC89C8 /* ShapefileTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ShapefileTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
60 | 035D10061C9E1FB200BC89C8 /* ShapefileTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShapefileTests.swift; sourceTree = ""; };
61 | 035D10081C9E1FB200BC89C8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
62 | 035D100D1C9E1FFA00BC89C8 /* Kantone.dbf */ = {isa = PBXFileReference; lastKnownFileType = file; path = Kantone.dbf; sourceTree = ""; };
63 | 035D100E1C9E1FFA00BC89C8 /* Kantone.prj */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Kantone.prj; sourceTree = ""; };
64 | 035D100F1C9E1FFA00BC89C8 /* Kantone.sbn */ = {isa = PBXFileReference; lastKnownFileType = file; path = Kantone.sbn; sourceTree = ""; };
65 | 035D10101C9E1FFA00BC89C8 /* Kantone.sbx */ = {isa = PBXFileReference; lastKnownFileType = file; path = Kantone.sbx; sourceTree = ""; };
66 | 035D10111C9E1FFA00BC89C8 /* Kantone.shp */ = {isa = PBXFileReference; lastKnownFileType = file; path = Kantone.shp; sourceTree = ""; };
67 | 035D10121C9E1FFA00BC89C8 /* Kantone.shp.xml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = Kantone.shp.xml; sourceTree = ""; };
68 | 035D10131C9E1FFA00BC89C8 /* Kantone.shx */ = {isa = PBXFileReference; lastKnownFileType = file; path = Kantone.shx; sourceTree = ""; };
69 | 03B708871CA5AEE700F3B3A0 /* data_sources.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = data_sources.txt; sourceTree = ""; };
70 | 03B708881CA5B1A000F3B3A0 /* plz_p1.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = plz_p1.txt; sourceTree = ""; };
71 | 03B708891CA5F08700F3B3A0 /* CanvasView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CanvasView.swift; path = ../vendor/CanvasView.swift; sourceTree = ""; };
72 | 03B7088B1CA5F6D200F3B3A0 /* ShapefileBitmap.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShapefileBitmap.swift; sourceTree = ""; };
73 | 03B708921CA73C2400F3B3A0 /* ShapefileView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShapefileView.swift; sourceTree = ""; };
74 | /* End PBXFileReference section */
75 |
76 | /* Begin PBXFrameworksBuildPhase section */
77 | 035D0FEB1C9E1EA200BC89C8 /* Frameworks */ = {
78 | isa = PBXFrameworksBuildPhase;
79 | buildActionMask = 2147483647;
80 | files = (
81 | );
82 | runOnlyForDeploymentPostprocessing = 0;
83 | };
84 | 035D10011C9E1FB200BC89C8 /* Frameworks */ = {
85 | isa = PBXFrameworksBuildPhase;
86 | buildActionMask = 2147483647;
87 | files = (
88 | );
89 | runOnlyForDeploymentPostprocessing = 0;
90 | };
91 | /* End PBXFrameworksBuildPhase section */
92 |
93 | /* Begin PBXGroup section */
94 | 0344B4DE1C9EFB02001DCDAB /* vendor */ = {
95 | isa = PBXGroup;
96 | children = (
97 | 0344B4DF1C9EFB02001DCDAB /* BinUtils.swift */,
98 | 0344B4E01C9EFB02001DCDAB /* BitmapCanvas.swift */,
99 | 0344B4E11C9EFB02001DCDAB /* X11Colors.swift */,
100 | );
101 | path = vendor;
102 | sourceTree = "";
103 | };
104 | 0344B4E81C9EFB28001DCDAB /* data */ = {
105 | isa = PBXGroup;
106 | children = (
107 | 03B708871CA5AEE700F3B3A0 /* data_sources.txt */,
108 | 03B708881CA5B1A000F3B3A0 /* plz_p1.txt */,
109 | 032E45ED1CA3E12A006C4622 /* Conditions_d'exploitation-Copyright_OFS_GEOSTAT.pdf */,
110 | 032E45EA1CA3E0CC006C4622 /* g2g15.shx */,
111 | 032E45EB1CA3E0CC006C4622 /* g2g15.shp */,
112 | 032E45EC1CA3E0CC006C4622 /* g2g15.dbf */,
113 | 032E45E71CA3E07E006C4622 /* g1k15.dbf */,
114 | 032E45E81CA3E07E006C4622 /* g1k15.shp */,
115 | 032E45E91CA3E07E006C4622 /* g1k15.shx */,
116 | );
117 | path = data;
118 | sourceTree = "";
119 | };
120 | 035D0FE51C9E1EA200BC89C8 = {
121 | isa = PBXGroup;
122 | children = (
123 | 0344B4DD1C9EEAFF001DCDAB /* README.md */,
124 | 0344B4E81C9EFB28001DCDAB /* data */,
125 | 0344B4DE1C9EFB02001DCDAB /* vendor */,
126 | 03B708911CA60CF800F3B3A0 /* viewers */,
127 | 035D0FF01C9E1EA200BC89C8 /* Shapefile */,
128 | 035D10051C9E1FB200BC89C8 /* ShapefileTests */,
129 | 035D0FEF1C9E1EA200BC89C8 /* Products */,
130 | );
131 | sourceTree = "";
132 | };
133 | 035D0FEF1C9E1EA200BC89C8 /* Products */ = {
134 | isa = PBXGroup;
135 | children = (
136 | 035D0FEE1C9E1EA200BC89C8 /* Shapefile */,
137 | 035D10041C9E1FB200BC89C8 /* ShapefileTests.xctest */,
138 | );
139 | name = Products;
140 | sourceTree = "";
141 | };
142 | 035D0FF01C9E1EA200BC89C8 /* Shapefile */ = {
143 | isa = PBXGroup;
144 | children = (
145 | 035D0FFE1C9E1EB200BC89C8 /* Shapefile.swift */,
146 | 035D0FF11C9E1EA200BC89C8 /* main.swift */,
147 | );
148 | path = Shapefile;
149 | sourceTree = "";
150 | };
151 | 035D10051C9E1FB200BC89C8 /* ShapefileTests */ = {
152 | isa = PBXGroup;
153 | children = (
154 | 035D100C1C9E1FFA00BC89C8 /* Kantone */,
155 | 035D10061C9E1FB200BC89C8 /* ShapefileTests.swift */,
156 | 035D10081C9E1FB200BC89C8 /* Info.plist */,
157 | );
158 | path = ShapefileTests;
159 | sourceTree = "";
160 | };
161 | 035D100C1C9E1FFA00BC89C8 /* Kantone */ = {
162 | isa = PBXGroup;
163 | children = (
164 | 035D100D1C9E1FFA00BC89C8 /* Kantone.dbf */,
165 | 035D100E1C9E1FFA00BC89C8 /* Kantone.prj */,
166 | 035D100F1C9E1FFA00BC89C8 /* Kantone.sbn */,
167 | 035D10101C9E1FFA00BC89C8 /* Kantone.sbx */,
168 | 035D10111C9E1FFA00BC89C8 /* Kantone.shp */,
169 | 035D10121C9E1FFA00BC89C8 /* Kantone.shp.xml */,
170 | 035D10131C9E1FFA00BC89C8 /* Kantone.shx */,
171 | );
172 | path = Kantone;
173 | sourceTree = "";
174 | };
175 | 03B708911CA60CF800F3B3A0 /* viewers */ = {
176 | isa = PBXGroup;
177 | children = (
178 | 03B7088B1CA5F6D200F3B3A0 /* ShapefileBitmap.swift */,
179 | 03B708891CA5F08700F3B3A0 /* CanvasView.swift */,
180 | 03B708921CA73C2400F3B3A0 /* ShapefileView.swift */,
181 | );
182 | name = viewers;
183 | path = Shapefile;
184 | sourceTree = "";
185 | };
186 | /* End PBXGroup section */
187 |
188 | /* Begin PBXNativeTarget section */
189 | 035D0FED1C9E1EA200BC89C8 /* Shapefile */ = {
190 | isa = PBXNativeTarget;
191 | buildConfigurationList = 035D0FF51C9E1EA200BC89C8 /* Build configuration list for PBXNativeTarget "Shapefile" */;
192 | buildPhases = (
193 | 035D0FEA1C9E1EA200BC89C8 /* Sources */,
194 | 035D0FEB1C9E1EA200BC89C8 /* Frameworks */,
195 | 035D0FEC1C9E1EA200BC89C8 /* CopyFiles */,
196 | );
197 | buildRules = (
198 | );
199 | dependencies = (
200 | );
201 | name = Shapefile;
202 | productName = Shapefile;
203 | productReference = 035D0FEE1C9E1EA200BC89C8 /* Shapefile */;
204 | productType = "com.apple.product-type.tool";
205 | };
206 | 035D10031C9E1FB200BC89C8 /* ShapefileTests */ = {
207 | isa = PBXNativeTarget;
208 | buildConfigurationList = 035D10091C9E1FB200BC89C8 /* Build configuration list for PBXNativeTarget "ShapefileTests" */;
209 | buildPhases = (
210 | 035D10001C9E1FB200BC89C8 /* Sources */,
211 | 035D10011C9E1FB200BC89C8 /* Frameworks */,
212 | 035D10021C9E1FB200BC89C8 /* Resources */,
213 | );
214 | buildRules = (
215 | );
216 | dependencies = (
217 | );
218 | name = ShapefileTests;
219 | productName = ShapefileTests;
220 | productReference = 035D10041C9E1FB200BC89C8 /* ShapefileTests.xctest */;
221 | productType = "com.apple.product-type.bundle.unit-test";
222 | };
223 | /* End PBXNativeTarget section */
224 |
225 | /* Begin PBXProject section */
226 | 035D0FE61C9E1EA200BC89C8 /* Project object */ = {
227 | isa = PBXProject;
228 | attributes = {
229 | LastSwiftUpdateCheck = 0720;
230 | LastUpgradeCheck = 0720;
231 | ORGANIZATIONNAME = "Nicolas Seriot";
232 | TargetAttributes = {
233 | 035D0FED1C9E1EA200BC89C8 = {
234 | CreatedOnToolsVersion = 7.2.1;
235 | LastSwiftMigration = 0800;
236 | };
237 | 035D10031C9E1FB200BC89C8 = {
238 | CreatedOnToolsVersion = 7.2.1;
239 | };
240 | };
241 | };
242 | buildConfigurationList = 035D0FE91C9E1EA200BC89C8 /* Build configuration list for PBXProject "Shapefile" */;
243 | compatibilityVersion = "Xcode 3.2";
244 | developmentRegion = English;
245 | hasScannedForEncodings = 0;
246 | knownRegions = (
247 | en,
248 | );
249 | mainGroup = 035D0FE51C9E1EA200BC89C8;
250 | productRefGroup = 035D0FEF1C9E1EA200BC89C8 /* Products */;
251 | projectDirPath = "";
252 | projectRoot = "";
253 | targets = (
254 | 035D0FED1C9E1EA200BC89C8 /* Shapefile */,
255 | 035D10031C9E1FB200BC89C8 /* ShapefileTests */,
256 | );
257 | };
258 | /* End PBXProject section */
259 |
260 | /* Begin PBXResourcesBuildPhase section */
261 | 035D10021C9E1FB200BC89C8 /* Resources */ = {
262 | isa = PBXResourcesBuildPhase;
263 | buildActionMask = 2147483647;
264 | files = (
265 | 035D10161C9E1FFA00BC89C8 /* Kantone.sbn in Resources */,
266 | 035D10141C9E1FFA00BC89C8 /* Kantone.dbf in Resources */,
267 | 035D10171C9E1FFA00BC89C8 /* Kantone.sbx in Resources */,
268 | 035D10151C9E1FFA00BC89C8 /* Kantone.prj in Resources */,
269 | 035D10181C9E1FFA00BC89C8 /* Kantone.shp in Resources */,
270 | 035D101A1C9E1FFA00BC89C8 /* Kantone.shx in Resources */,
271 | 035D10191C9E1FFA00BC89C8 /* Kantone.shp.xml in Resources */,
272 | );
273 | runOnlyForDeploymentPostprocessing = 0;
274 | };
275 | /* End PBXResourcesBuildPhase section */
276 |
277 | /* Begin PBXSourcesBuildPhase section */
278 | 035D0FEA1C9E1EA200BC89C8 /* Sources */ = {
279 | isa = PBXSourcesBuildPhase;
280 | buildActionMask = 2147483647;
281 | files = (
282 | 035D0FFF1C9E1EB200BC89C8 /* Shapefile.swift in Sources */,
283 | 0344B4E21C9EFB02001DCDAB /* BinUtils.swift in Sources */,
284 | 03B708931CA73C2400F3B3A0 /* ShapefileView.swift in Sources */,
285 | 0344B4E61C9EFB02001DCDAB /* X11Colors.swift in Sources */,
286 | 0344B4E41C9EFB02001DCDAB /* BitmapCanvas.swift in Sources */,
287 | 03B7088A1CA5F08700F3B3A0 /* CanvasView.swift in Sources */,
288 | 03B7088C1CA5F6D200F3B3A0 /* ShapefileBitmap.swift in Sources */,
289 | 035D0FF21C9E1EA200BC89C8 /* main.swift in Sources */,
290 | );
291 | runOnlyForDeploymentPostprocessing = 0;
292 | };
293 | 035D10001C9E1FB200BC89C8 /* Sources */ = {
294 | isa = PBXSourcesBuildPhase;
295 | buildActionMask = 2147483647;
296 | files = (
297 | 035D10071C9E1FB200BC89C8 /* ShapefileTests.swift in Sources */,
298 | 0344B4E31C9EFB02001DCDAB /* BinUtils.swift in Sources */,
299 | 0344B4E71C9EFB02001DCDAB /* X11Colors.swift in Sources */,
300 | 0344B4E51C9EFB02001DCDAB /* BitmapCanvas.swift in Sources */,
301 | 035D101B1C9E208F00BC89C8 /* Shapefile.swift in Sources */,
302 | );
303 | runOnlyForDeploymentPostprocessing = 0;
304 | };
305 | /* End PBXSourcesBuildPhase section */
306 |
307 | /* Begin XCBuildConfiguration section */
308 | 035D0FF31C9E1EA200BC89C8 /* Debug */ = {
309 | isa = XCBuildConfiguration;
310 | buildSettings = {
311 | ALWAYS_SEARCH_USER_PATHS = NO;
312 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
313 | CLANG_CXX_LIBRARY = "libc++";
314 | CLANG_ENABLE_MODULES = YES;
315 | CLANG_ENABLE_OBJC_ARC = YES;
316 | CLANG_WARN_BOOL_CONVERSION = YES;
317 | CLANG_WARN_CONSTANT_CONVERSION = YES;
318 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
319 | CLANG_WARN_EMPTY_BODY = YES;
320 | CLANG_WARN_ENUM_CONVERSION = YES;
321 | CLANG_WARN_INT_CONVERSION = YES;
322 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
323 | CLANG_WARN_UNREACHABLE_CODE = YES;
324 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
325 | CODE_SIGN_IDENTITY = "-";
326 | COPY_PHASE_STRIP = NO;
327 | DEBUG_INFORMATION_FORMAT = dwarf;
328 | ENABLE_STRICT_OBJC_MSGSEND = YES;
329 | ENABLE_TESTABILITY = YES;
330 | GCC_C_LANGUAGE_STANDARD = gnu99;
331 | GCC_DYNAMIC_NO_PIC = NO;
332 | GCC_NO_COMMON_BLOCKS = YES;
333 | GCC_OPTIMIZATION_LEVEL = 0;
334 | GCC_PREPROCESSOR_DEFINITIONS = (
335 | "DEBUG=1",
336 | "$(inherited)",
337 | );
338 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
339 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
340 | GCC_WARN_UNDECLARED_SELECTOR = YES;
341 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
342 | GCC_WARN_UNUSED_FUNCTION = YES;
343 | GCC_WARN_UNUSED_VARIABLE = YES;
344 | MACOSX_DEPLOYMENT_TARGET = 10.11;
345 | MTL_ENABLE_DEBUG_INFO = YES;
346 | ONLY_ACTIVE_ARCH = YES;
347 | SDKROOT = macosx;
348 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
349 | };
350 | name = Debug;
351 | };
352 | 035D0FF41C9E1EA200BC89C8 /* Release */ = {
353 | isa = XCBuildConfiguration;
354 | buildSettings = {
355 | ALWAYS_SEARCH_USER_PATHS = NO;
356 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
357 | CLANG_CXX_LIBRARY = "libc++";
358 | CLANG_ENABLE_MODULES = YES;
359 | CLANG_ENABLE_OBJC_ARC = YES;
360 | CLANG_WARN_BOOL_CONVERSION = YES;
361 | CLANG_WARN_CONSTANT_CONVERSION = YES;
362 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
363 | CLANG_WARN_EMPTY_BODY = YES;
364 | CLANG_WARN_ENUM_CONVERSION = YES;
365 | CLANG_WARN_INT_CONVERSION = YES;
366 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
367 | CLANG_WARN_UNREACHABLE_CODE = YES;
368 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
369 | CODE_SIGN_IDENTITY = "-";
370 | COPY_PHASE_STRIP = NO;
371 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
372 | ENABLE_NS_ASSERTIONS = NO;
373 | ENABLE_STRICT_OBJC_MSGSEND = YES;
374 | GCC_C_LANGUAGE_STANDARD = gnu99;
375 | GCC_NO_COMMON_BLOCKS = YES;
376 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
377 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
378 | GCC_WARN_UNDECLARED_SELECTOR = YES;
379 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
380 | GCC_WARN_UNUSED_FUNCTION = YES;
381 | GCC_WARN_UNUSED_VARIABLE = YES;
382 | MACOSX_DEPLOYMENT_TARGET = 10.11;
383 | MTL_ENABLE_DEBUG_INFO = NO;
384 | SDKROOT = macosx;
385 | };
386 | name = Release;
387 | };
388 | 035D0FF61C9E1EA200BC89C8 /* Debug */ = {
389 | isa = XCBuildConfiguration;
390 | buildSettings = {
391 | PRODUCT_NAME = "$(TARGET_NAME)";
392 | SWIFT_VERSION = 3.0;
393 | };
394 | name = Debug;
395 | };
396 | 035D0FF71C9E1EA200BC89C8 /* Release */ = {
397 | isa = XCBuildConfiguration;
398 | buildSettings = {
399 | PRODUCT_NAME = "$(TARGET_NAME)";
400 | SWIFT_VERSION = 3.0;
401 | };
402 | name = Release;
403 | };
404 | 035D100A1C9E1FB200BC89C8 /* Debug */ = {
405 | isa = XCBuildConfiguration;
406 | buildSettings = {
407 | COMBINE_HIDPI_IMAGES = YES;
408 | INFOPLIST_FILE = ShapefileTests/Info.plist;
409 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
410 | PRODUCT_BUNDLE_IDENTIFIER = ch.seriot.ShapefileTests;
411 | PRODUCT_NAME = "$(TARGET_NAME)";
412 | SWIFT_VERSION = 3.0;
413 | };
414 | name = Debug;
415 | };
416 | 035D100B1C9E1FB200BC89C8 /* Release */ = {
417 | isa = XCBuildConfiguration;
418 | buildSettings = {
419 | COMBINE_HIDPI_IMAGES = YES;
420 | INFOPLIST_FILE = ShapefileTests/Info.plist;
421 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
422 | PRODUCT_BUNDLE_IDENTIFIER = ch.seriot.ShapefileTests;
423 | PRODUCT_NAME = "$(TARGET_NAME)";
424 | SWIFT_VERSION = 3.0;
425 | };
426 | name = Release;
427 | };
428 | /* End XCBuildConfiguration section */
429 |
430 | /* Begin XCConfigurationList section */
431 | 035D0FE91C9E1EA200BC89C8 /* Build configuration list for PBXProject "Shapefile" */ = {
432 | isa = XCConfigurationList;
433 | buildConfigurations = (
434 | 035D0FF31C9E1EA200BC89C8 /* Debug */,
435 | 035D0FF41C9E1EA200BC89C8 /* Release */,
436 | );
437 | defaultConfigurationIsVisible = 0;
438 | defaultConfigurationName = Release;
439 | };
440 | 035D0FF51C9E1EA200BC89C8 /* Build configuration list for PBXNativeTarget "Shapefile" */ = {
441 | isa = XCConfigurationList;
442 | buildConfigurations = (
443 | 035D0FF61C9E1EA200BC89C8 /* Debug */,
444 | 035D0FF71C9E1EA200BC89C8 /* Release */,
445 | );
446 | defaultConfigurationIsVisible = 0;
447 | defaultConfigurationName = Release;
448 | };
449 | 035D10091C9E1FB200BC89C8 /* Build configuration list for PBXNativeTarget "ShapefileTests" */ = {
450 | isa = XCConfigurationList;
451 | buildConfigurations = (
452 | 035D100A1C9E1FB200BC89C8 /* Debug */,
453 | 035D100B1C9E1FB200BC89C8 /* Release */,
454 | );
455 | defaultConfigurationIsVisible = 0;
456 | defaultConfigurationName = Release;
457 | };
458 | /* End XCConfigurationList section */
459 | };
460 | rootObject = 035D0FE61C9E1EA200BC89C8 /* Project object */;
461 | }
462 |
--------------------------------------------------------------------------------