├── 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 | Switzerland Altitude 92 | 93 | Switzerland ZIP Codes 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 | --------------------------------------------------------------------------------