├── bower.json ├── changelog ├── lib ├── geostats.css ├── geostats.js └── geostats.min.js ├── package.json ├── readme.md ├── sample.html ├── sample_shp.html └── tests ├── js-shapefile-to-geojson ├── dbf.js ├── readme.md ├── shapefile.js ├── stream.js └── testdata │ ├── world.dbf │ └── world.shp ├── openlayers_sample.js ├── qunit_test.html └── test.json /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "geostats", 3 | "repo": "simogeo/geostats", 4 | "description": "a tiny and standalone javascript library for classification and basic statistics", 5 | "keywords": ["mapping", "map", "classification", "choropleth", "stats", "statistics", "math"], 6 | "dependencies": {}, 7 | "development": {}, 8 | "main": [ 9 | "lib/geostats.min.js" 10 | ] 11 | } -------------------------------------------------------------------------------- /changelog: -------------------------------------------------------------------------------- 1 | From version 1.8.0 to 2.0.0 2 | --------------------------- 3 | - Typescript compatibility | issue #51 4 | - Prevent Jenks breaks error when data.length is less than nbClass | issue #57 5 | - Switch from 1.8.0 to 2.0.0 because of npm error 6 | 7 | 8 | From version 1.7.0 to 1.8.0 9 | --------------------------- 10 | - Fixing bug on counter when using using getClassUniqueValues() method | issue #50 11 | - Ensure nbClass param is an integer | issue #45 12 | - Optimized getClassJenks() method | issue #49 13 | - Old Jenks method still available by calling getClassJenks2() method 14 | - Fix the default legend style, so items are aligned vertically | issue #48 15 | 16 | 17 | From version 1.6.0 to 1.7.0 18 | --------------------------- 19 | - Creating temporary variables instead of calling functions several times | issue #40 20 | - Fix minified version | issue #43 21 | - Updating NPM | issue #43 22 | 23 | 24 | From version 1.5.0 to 1.6.0 25 | --------------------------- 26 | - Typo bug converting string to float | issue #32 27 | - Force precision to 20 if user defined is superior - to prevent browser error | issue #34 28 | - Mean calculation outside loop | issue #35 29 | - Min / Max methods handled without using Math function | supposed to prevent issue #33 30 | - Cast values to Float on decimalFormat method | issue #36 31 | 32 | 33 | From version 1.4.0 to 1.5.0 34 | --------------------------- 35 | - Reset attributes after setting a new serie | issue #29 36 | 37 | 38 | From version 1.3.0 to 1.4.0 39 | --------------------------- 40 | - Return legend to wanted float format using decimals if needed - with 'distinct' mode | issue #26 41 | - Ability to match min / max bounds when calling getClassStdDeviation() | issue #27 42 | 43 | 44 | From version 1.2.0 to 1.3.0 45 | --------------------------- 46 | - Fixing decimalFormat() bug returned values | issue #19 47 | - Fixing Quantile classification bug | issue #18 48 | - Modularisation, for use with nodeJS or requireJS | issue #16 49 | - Ability to reverse legend order | issue #24 50 | - Introducing a silent mode on input errors | issue #25 51 | 52 | 53 | From version 1.1.0 to 1.2.0 54 | --------------------------- 55 | - Introducing geometric progression classification (getGeometricProgression() method) 56 | - Introducing user defined classification (setManualClassification() method) 57 | - Introducing CommonJS and NodeJS or RequireJS module formats 58 | - Use of indexOf native version 59 | 60 | 61 | From version 1.0.0 to 1.1.0 62 | --------------------------- 63 | - Removing useless lib/jenks.util.js file 64 | - Introducing changelog -------------------------------------------------------------------------------- /lib/geostats.css: -------------------------------------------------------------------------------- 1 | .geostats-legend div { 2 | margin:3px 10px 5px 10px; 3 | clear:left; 4 | } 5 | 6 | .geostats-legend-title { 7 | font-weight: bold; 8 | margin-bottom: 4px; 9 | } 10 | 11 | .geostats-legend-block { 12 | border: 1px solid #555555; 13 | display: block; 14 | float: left; 15 | height: 12px; 16 | margin: 0 5px 0 20px; 17 | width: 20px; 18 | } 19 | 20 | .geostats-legend-counter { 21 | font-size: 0.8em; 22 | color:#666; 23 | font-style: italic; 24 | } 25 | -------------------------------------------------------------------------------- /lib/geostats.js: -------------------------------------------------------------------------------- 1 | /** 2 | * geostats() is a tiny and standalone javascript library for classification 3 | * Project page - https://github.com/simogeo/geostats 4 | * Copyright (c) 2011 Simon Georget, http://www.intermezzo-coop.eu 5 | * Licensed under the MIT license 6 | */ 7 | 8 | 9 | (function (definition) { 10 | // This file will function properly as a 30 | ``` 31 | 32 | **Please see the samples page to understand how it works!** 33 | 34 | API 35 | ======================== 36 | 37 | Attributes : 38 | -------------- 39 | 40 | - *serie* : contains the statistic population. Default : empty. 41 | - *separator* : used to separate ranges values, by default : ' - ' 42 | - *legendSeparator* : by default, equals to *separator* value 43 | - *method* : give information regarding the classification method used. Default : empty. 44 | - *bounds* : classification bounds 45 | - *ranges* : classification ranges 46 | - *colors* : classification colors 47 | - *debug* : default value `false`. When set to `true` , provide useful debug message regarding objects and properties. 48 | - *silent* : default value `false`. If silent, do no trigger alert() message when inputs are incorrects but display console.log() messages 49 | 50 | 51 | Methods : 52 | --------- 53 | 54 | 55 | **Statistics :** 56 | 57 | - *min()* : return the min value 58 | - *max()* : return the max value 59 | - *sum()* : return the sum of the population 60 | - *pop()* : return the number of individuals 61 | - *mean()* : return the mean 62 | - *median()* : return the median 63 | - *variance()* : return the variance 64 | - *stddev()* : return the standard deviation 65 | - *cov()* : return the coefficient of variation 66 | 67 | 68 | **Classification :** 69 | 70 | - *getClassEqInterval(nbClass)* : Perform an equal interval classification and return bounds into an array. Alias : *getEqInterval(nbClass)* 71 | - *getClassStdDeviation(nbClass)* : Perform a standard deviation classification and return bounds into an array. Alias : *getStdDeviation(nbClass)* 72 | - *getClassArithmeticProgression(nbClass)* : Perform an arithmetic progression classification and return bounds into an array. Alias : *getArithmeticProgression(nbClass)* 73 | - *getClassGeometricProgression(nbClass)* : Perform a geometric progression classification and return bounds into an array. Alias : *getGeometricProgression(nbClass)* 74 | - *getClassQuantile(nbClass)* : Perform a quantile classification and return bounds into an array. Alias : *getQuantile(nbClass)* 75 | - *getClassJenks(nbClass)* : Perform a Jenks classification and return bounds into an array. Alias : *getJenks(nbClass)* - optimised version / see #49 76 | - *getClassJenks2(nbClass)* : Perform a Jenks classification and return bounds into an array. Alias : *getJenks2(nbClass)* - older version 77 | - *getClassUniqueValues()* : Perform a unique values classification and return bounds (values) into an array. Alias : *getUniqueValues()* 78 | - *setClassManually()* : Set a user defined classification based on passed array (Same array is returned). Useful to automatically set bounds/ranges and generate legend. 79 | 80 | 81 | 82 | **Constructor methods :** 83 | 84 | - *setSerie()* : fill up the *serie* attribute 85 | - *setColors()* : fill up the *colors* attribute 86 | - *setPrecision()* : set precision on serie - only useful for float series. Can take no value (for automatic precision), or an integer between 0-20. By default, the precision will be computed automatically by *geostats*. 87 | 88 | 89 | **Getters methods :** 90 | 91 | - *getRanges(array)* : return an array of classes range (*ranges* value) 92 | - *getRangeNum()* : return the number/index of this.ranges that value falls into 93 | - *getHtmlLegend(colors, legend, callback, mode, order)* : return a legend in html format.
`colors` defines an array of colors; `legend` is used for giving the legend a different title; with `counter`, a particular counter value can be displayed; a function which should be applied to the legend boundaries is used in place of `callback`; `mode` is for different display modes of the value ranges; `order` refers to the sort order of values. For further details, please refer [to the code comment](https://github.com/simogeo/geostats/blob/master/lib/geostats.js#L1054) which accompanies the getHtmlLegend method. 94 | - *getSortedlist()* : return the sorted serie in text format 95 | - *getClass()* : return a given value class 96 | 97 | 98 | **Internals methods :** 99 | 100 | - *_nodata()* : check if *serie* attribute if not empty 101 | - *_hasNegativeValue()* : check if the serie contains any negative values 102 | - *_hasZeroValue()* : check if the serie contains zero values 103 | - *sorted()* : return the sorted (asc) serie 104 | - *info()* : return information about the population in text format 105 | - *setRanges()* : fill up the *ranges* attribute (array of classes range) 106 | - *doCount()* : perform count feature by classes, used to display count feature in legend 107 | 108 | 109 | *Note : If you are looking for a nice JS library to format numbers for displaying, just rely on [numeraljs](http://numeraljs.com/).* 110 | 111 | 112 | MIT LICENSE 113 | ======================== 114 | 115 | Copyright (c) 2011 Simon Georget 116 | 117 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 118 | 119 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 120 | 121 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 122 | 123 | 124 | -------------------------------------------------------------------------------- /sample.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | geostats.js (by simon georget) 6 | 7 | 8 | 9 | 10 | 11 | 12 | 59 | 161 | 185 | 186 | 187 | 188 |
189 |

geostats - Javascript classification library samples

190 |

Github page - download (from github)

191 | 408 |
409 |

Sample 8 - a choropleth representation (using openlayers) with JSON input

410 |
411 |
412 |
413 |
414 |

Sample 9 - dynamic HTML legend with user input, feature count, callback function and display mode option

415 |
416 |

Call to legend method with default options

417 |
418 |
419 |

Call to legend method with options : customized text, callback function and 'distinct' mode. Decimal precision has been set to 2.

420 |
421 |
422 |

Call to legend method with options : 'discontinuous' mode, and descending order.

423 |
424 |
425 |
426 |
427 |

Sample 10 - Handle unique values

428 |
429 |
430 |
431 |
432 |

Sample 11 - a choropleth representation (using openlayers) with JSON input and Chroma.js

433 |
434 |
435 | 			
436 | 				// getting the color ramp from chroma by passing min, max values and number of classes
437 | 				var color_x = chroma.scale('RdYlGn').domain([serie7.min(),serie7.max()], 5).colors();
438 | 				
439 | 				// setting colors into geostats
440 | 				serie7.setColors(color_x);
441 | 				
442 | 				// rendering the html legend with geostats
443 | 				jQuery('#legend5').html(serie7.getHtmlLegend(null, null, 1));
444 | 				
445 | 				// displaying map with openlayers
446 | 				initChromaSample(color_x, class_x);
447 | 			
448 |
449 |
450 |
451 |
452 |
453 |

Sample 12 - Reading shapefile and creating choropleth map from attribute data

454 |
Using geostats and js-shapefile-to-geojson (by Marc Harter) to create thematic maps. Access demo page.
455 |
456 | 457 | 458 |
459 | 460 | -------------------------------------------------------------------------------- /sample_shp.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | geostats - js-shapefile-to-geojson integration sample 5 | 6 | 7 | 8 | 9 | 48 | 49 | 50 |
51 |

geostats - js-shapefile-to-geojson integration sample

52 |

Reading shapefile and creating choropleth map from attribute data

53 |
54 | 55 | 56 | 57 | 162 |
163 | 164 |
165 | 166 | 167 | 168 | -------------------------------------------------------------------------------- /tests/js-shapefile-to-geojson/dbf.js: -------------------------------------------------------------------------------- 1 | (function(window,undefined){ 2 | 3 | if(window.document && window.Worker){ 4 | var worker = new Worker("./tests/js-shapefile-to-geojson/dbf.js") 5 | 6 | var DBF = function(url, callback){ 7 | var 8 | w = this._worker = worker, 9 | t = this 10 | 11 | w.onmessage = function(e){ 12 | t.data = e.data 13 | if (callback) callback(e.data); 14 | }; 15 | 16 | w.postMessage(url) 17 | } 18 | 19 | window["DBF"] = DBF 20 | return 21 | } 22 | 23 | var IN_WORKER = !window.document 24 | if (IN_WORKER) { 25 | importScripts('stream.js') 26 | onmessage = function(e){ 27 | new DBF(e.data); 28 | }; 29 | } 30 | 31 | var 32 | DBASE_LEVEL = { 33 | "3": "dBASE Level 5", 34 | "4": "dBase Level 7" 35 | }, 36 | DBASE_FIELD_TYPE = { 37 | "N": "Number", 38 | "C": "Character", // binary 39 | "L": "Logical", 40 | "D": "Date", 41 | "M": "Memo", // binary 42 | "F": "Floating point", 43 | "B": "Binary", 44 | "G": "General", 45 | "P": "Picture", 46 | "Y": "Currency", 47 | "T": "DateTime", 48 | "I": "Integer", 49 | "V": "VariField", 50 | "X": "Variant", 51 | "@": "Timestamp", 52 | "O": "Double", 53 | "+": "Autoincrement", // (dBase Level 7) 54 | "O": "Double", // (dBase Level 7) 55 | "@": "Timestamp" // (dBase Level 7) 56 | } 57 | 58 | var DBF = function(url, callback){ 59 | var xhr = new XMLHttpRequest(); 60 | 61 | xhr.open("GET", url, false) 62 | xhr.overrideMimeType("text/plain; charset=x-user-defined") 63 | xhr.send() 64 | 65 | if(200 != xhr.status) 66 | throw "Unable to load " + url + " status: " + xhr.status 67 | 68 | this.stream = new Gordon.Stream(xhr.responseText) 69 | this.callback = callback 70 | 71 | this.readFileHeader() 72 | this.readFieldDescriptions() 73 | this.readRecords() 74 | 75 | this._postMessage() 76 | } 77 | 78 | DBF.prototype = { 79 | constructor: DBF, 80 | _postMessage: function() { 81 | var data = { 82 | header: this.header, 83 | fields: this.fields, 84 | records: this.records 85 | } 86 | if (IN_WORKER) postMessage(data) 87 | else if (this.callback) this.callback(data) 88 | }, 89 | readFileHeader: function(){ 90 | var s = this.stream, 91 | header = this.header = {}, 92 | date = new Date; 93 | 94 | header.version = DBASE_LEVEL[s.readSI8()] 95 | 96 | // Date of last update; in YYMMDD format. Each byte contains the number as a binary. YY is added to a base of 1900 decimal to determine the actual year. Therefore, YY has possible values from 0x00-0xFF, which allows for a range from 1900-2155. 97 | date.setUTCFullYear(1900 + s.readSI8()) 98 | date.setUTCMonth(s.readSI8()) 99 | date.setUTCDate(s.readSI8()) 100 | 101 | header.lastUpdated = date 102 | 103 | // Number of records in file 104 | header.numRecords = s.readSI32() 105 | 106 | // Position of first data record 107 | header.firstRecordPosition = s.readSI16() 108 | 109 | // Length of one data record, including delete flag 110 | header.recordLength = s.readSI16() 111 | 112 | // Reserved; filled with zeros 113 | s.offset += 16 114 | 115 | /* 116 | Table flags: 117 | 0x01 file has a structural .cdx 118 | 0x02 file has a Memo field 119 | 0x04 file is a database (.dbc) 120 | This byte can contain the sum of any of the above values. For example, the value 0x03 indicates the table has a structural .cdx and a Memo field. 121 | */ 122 | header.flags = s.readSI8() 123 | 124 | // Code page mark 125 | header.codePageMark = s.readSI8() 126 | 127 | // Reserved; filled with zeros. 128 | s.offset += 2 129 | 130 | }, 131 | readFieldDescriptions: function(){ 132 | var s = this.stream, 133 | fields = [], 134 | field 135 | 136 | while (s.readSI8() != 0x0D) { 137 | s.offset-- 138 | field = {} 139 | 140 | // Field name with a maximum of 10 characters. If less than 10, it is padded with null characters (0x00). 141 | field.name = s.readString(11).replace(/\u0000/g,"") 142 | 143 | field.type = DBASE_FIELD_TYPE[s.readString(1)] 144 | 145 | // Displacement of field in record 146 | field.fieldDisplacement = s.readSI32() 147 | 148 | // Length of field (in bytes) 149 | field.fieldLength = s.readUI8() 150 | 151 | // Number of decimal places 152 | field.decimals = s.readSI8() 153 | 154 | /* 155 | Field flags: 156 | 0x01 System Column (not visible to user) 157 | 0x02 Column can store null values 158 | 0x04 Binary column (for CHAR and MEMO only) 159 | 0x06 (0x02+0x04) When a field is NULL and binary (Integer, Currency, and Character/Memo fields) 160 | 0x0C Column is autoincrementing 161 | */ 162 | field.flags = s.readSI8() 163 | 164 | // Value of autoincrement Next value 165 | field.autoincrementNextValue = s.readSI32() 166 | 167 | // Value of autoincrement Step value 168 | field.autoincrementStepValue = s.readSI8() 169 | 170 | // Reserved 171 | s.offset += 8 172 | 173 | fields.push(field) 174 | } 175 | 176 | this.fields = fields 177 | 178 | }, 179 | readRecords: function(){ 180 | var s = this.stream, 181 | numRecords = this.header.numRecords, 182 | recordsOffset = this.header.firstRecordPosition, 183 | recordSize = this.header.recordLength, 184 | fields = this.fields, 185 | numFields = fields.length, 186 | records = [], 187 | field, record 188 | 189 | for (var index = 0; index < numRecords; index++) { 190 | s.offset = recordsOffset + index * recordSize 191 | 192 | record = {} 193 | 194 | // Data records begin with a delete flag byte. If this byte is an ASCII space (0x20), the record is not deleted. If the first byte is an asterisk (0x2A), the record is deleted 195 | record._isDeleted = s.readSI8() == 42 196 | 197 | for(var i = 0; i < numFields; i++){ 198 | field = fields[i] 199 | record[field.name] = s.readString(field.fieldLength).trim(); 200 | } 201 | 202 | records.push(record); 203 | } 204 | 205 | this.records = records 206 | 207 | } 208 | } 209 | 210 | window["DBF"] = DBF; 211 | 212 | })(self) 213 | 214 | -------------------------------------------------------------------------------- /tests/js-shapefile-to-geojson/readme.md: -------------------------------------------------------------------------------- 1 | This project allows a user to load Shapefiles and DBFs into the browser with JavaScript. 2 | Outputs as [GeoJSON](http://geojson.org/) for use with other Mapping APIs such as [OpenLayers](http://openlayers.org). 3 | 4 | Inspired by the excellent work by Tom Carden ([http://github.com/RandomEtc/shapefile-js/](http://github.com/RandomEtc/shapefile-js/)). 5 | 6 | ### Overview 7 | 8 | I just got this out there so nothing is minified. See index.html for an example of order. All files need to be in the same directory. This will use Web Workers if the browser support exists. Not recommended for large files, more of an experiment than anything. 9 | 10 | ### Usage 11 | 12 | You can use it to parse shapefiles (.shp) or dBase files (.dbf) or both. Here are some examples. 13 | 14 | Load Shapefile Only 15 | 16 | var shapefile = new Shapefile("myshapefile.shp",function(data){ 17 | // data returned 18 | }; 19 | 20 | Load DBF Only 21 | 22 | var dbf = new DBF("mydbf.dbf",function(data){ 23 | // data returned 24 | }; 25 | 26 | Load Shapefile w/ DBF Attributes 27 | 28 | var shapefile = new Shapefile({ 29 | shp: "myshape.dbf", 30 | dbf: "myshape.dbf" 31 | }, function(data){ 32 | // data returned 33 | }; 34 | 35 | Use with OpenLayers 36 | 37 | var 38 | parser = new OpenLayer.Format.GeoJSON(), 39 | features, 40 | shapefile = new Shapefile({ 41 | shp: "myshape.dbf", 42 | dbf: "myshape.dbf" 43 | }, function(data){ 44 | features = parser.read(data.geojson); 45 | }; 46 | 47 | ### Resources 48 | 49 | I used the technical descriptions found here to parse the binary: 50 | 51 | > [ESRI Shapefile Technical Description - PDF](http://www.esri.com/library/whitepapers/pdfs/shapefile.pdf) 52 | 53 | > [dBase (Xbase) File Format Description](http://www.dbf2002.com/dbf-file-format.html) 54 | 55 | ### Future 56 | 57 | I plan to implement (time permitting) some custom renderers like SVG or Canvas (besides using OpenLayers) to improve the speed. 58 | 59 | Feel free to hack at this, submit bugs, pull requests, and make it better. If you write a renderer, please push it back and I'll add it to the project. 60 | 61 | ### License 62 | 63 | (The MIT License) 64 | 65 | Copyright (c) 2010 Marc Harter <wavded@gmail.com> 66 | 67 | Permission is hereby granted, free of charge, to any person obtaining 68 | a copy of this software and associated documentation files (the 69 | 'Software'), to deal in the Software without restriction, including 70 | without limitation the rights to use, copy, modify, merge, publish, 71 | distribute, sublicense, and/or sell copies of the Software, and to 72 | permit persons to whom the Software is furnished to do so, subject to 73 | the following conditions: 74 | 75 | The above copyright notice and this permission notice shall be 76 | included in all copies or substantial portions of the Software. 77 | 78 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 79 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 80 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 81 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 82 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 83 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 84 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 85 | -------------------------------------------------------------------------------- /tests/js-shapefile-to-geojson/shapefile.js: -------------------------------------------------------------------------------- 1 | (function(window,undefined){ 2 | 3 | if(window.document && window.Worker){ 4 | var worker = new Worker("./tests/js-shapefile-to-geojson/shapefile.js") 5 | 6 | var Shapefile = function(o, callback){ 7 | var 8 | w = this.worker = worker, 9 | t = this, 10 | o = typeof o == "string" ? {shp: o} : o 11 | 12 | w.onmessage = function(e){ 13 | t.data = e.date 14 | if(callback) callback(e.data) 15 | } 16 | 17 | w.postMessage(["Load", o]) 18 | 19 | if(o.dbf) this.dbf = new DBF(o.dbf,function(data){ 20 | w.postMessage(["Add DBF Attributes", data]) 21 | }) 22 | } 23 | 24 | window["Shapefile"] = Shapefile 25 | return 26 | } 27 | 28 | var IN_WORKER = !window.document 29 | if (IN_WORKER) { 30 | importScripts('stream.js') 31 | onmessage = function(e){ 32 | switch (e.data[0]) { 33 | case "Load": 34 | window.shapefile = new Shapefile(e.data[1]) 35 | break 36 | case "Add DBF Attributes": 37 | window.shapefile.addDBFDataToGeoJSON(e.data[1]) 38 | window.shapefile._postMessage() 39 | break 40 | default: 41 | } 42 | }; 43 | } 44 | 45 | var SHAPE_TYPES = { 46 | "0": "Null Shape", 47 | "1": "Point", // standard shapes 48 | "3": "PolyLine", 49 | "5": "Polygon", 50 | "8": "MultiPoint", 51 | "11": "PointZ", // 3d shapes 52 | "13": "PolyLineZ", 53 | "15": "PolygonZ", 54 | "18": "MultiPointZ", 55 | "21": "PointM", // user-defined measurement shapes 56 | "23": "PolyLineM", 57 | "25": "PolygonM", 58 | "28": "MultiPointM", 59 | "31": "MultiPatch" 60 | } 61 | 62 | var Shapefile = function(o,callback){ 63 | var xhr = new XMLHttpRequest(), 64 | that = this, 65 | o = typeof o == "string" ? {shp: o} : o 66 | 67 | xhr.open("GET", o.shp, false) 68 | xhr.overrideMimeType("text/plain; charset=x-user-defined") 69 | xhr.send() 70 | 71 | if(200 != xhr.status) 72 | throw "Unable to load " + o.shp + " status: " + xhr.status 73 | 74 | this.url = o.shp 75 | this.stream = new Gordon.Stream(xhr.responseText) 76 | this.callback = callback 77 | 78 | this.readFileHeader() 79 | this.readRecords() 80 | this.formatIntoGeoJson() 81 | 82 | if(o.dbf) this.dbf = IN_WORKER ? 83 | null : 84 | new DBF(o.dbf,function(data){ 85 | that.addDBFDataToGeoJSON(data) 86 | that._postMessage() 87 | }) 88 | else this._postMessage 89 | } 90 | 91 | Shapefile.prototype = { 92 | constructor: Shapefile, 93 | _postMessage: function() { 94 | var data = { 95 | header: this.header, 96 | records: this.records, 97 | dbf: this.dbf, 98 | geojson: this.geojson 99 | } 100 | if (IN_WORKER) postMessage(data) 101 | else if (this.callback) this.callback(data) 102 | }, 103 | readFileHeader: function(){ 104 | var s = this.stream, 105 | header = this.header = {} 106 | 107 | // The main file header is fixed at 100 bytes in length 108 | if(s < 100) throw "Invalid Header Length" 109 | 110 | // File code (always hex value 0x0000270a) 111 | header.fileCode = s.readSI32(true) 112 | 113 | if(header.fileCode != parseInt(0x0000270a)) 114 | throw "Invalid File Code" 115 | 116 | // Unused; five uint32 117 | s.offset += 4 * 5 118 | 119 | // File length (in 16-bit words, including the header) 120 | header.fileLength = s.readSI32(true) * 2 121 | 122 | header.version = s.readSI32() 123 | 124 | header.shapeType = SHAPE_TYPES[s.readSI32()] 125 | 126 | // Minimum bounding rectangle (MBR) of all shapes contained within the shapefile; four doubles in the following order: min X, min Y, max X, max Y 127 | this._readBounds(header) 128 | 129 | // Z axis range 130 | header.rangeZ = { 131 | min: s.readDouble(), 132 | max: s.readDouble() 133 | } 134 | 135 | // User defined measurement range 136 | header.rangeM = { 137 | min: s.readDouble(), 138 | max: s.readDouble() 139 | } 140 | 141 | }, 142 | readRecords: function(){ 143 | var s = this.stream, 144 | records = this.records = [], 145 | record 146 | 147 | do { 148 | record = {} 149 | 150 | // Record number (1-based) 151 | record.id = s.readSI32(true) 152 | 153 | if(record.id == 0) break //no more records 154 | 155 | // Record length (in 16-bit words) 156 | record.length = s.readSI32(true) * 2 157 | 158 | record.shapeType = SHAPE_TYPES[s.readSI32()] 159 | 160 | // Read specific shape 161 | this["_read" + record.shapeType](record); 162 | 163 | records.push(record); 164 | 165 | } while(true); 166 | 167 | }, 168 | _readBounds: function(object){ 169 | var s = this.stream 170 | 171 | object.bounds = { 172 | left: s.readDouble(), 173 | bottom: s.readDouble(), 174 | right: s.readDouble(), 175 | top: s.readDouble() 176 | } 177 | 178 | return object 179 | }, 180 | _readParts: function(record){ 181 | var s = this.stream, 182 | nparts, 183 | parts = [] 184 | 185 | nparts = record.numParts = s.readSI32() 186 | 187 | // since number of points always proceeds number of parts, capture it now 188 | record.numPoints = s.readSI32() 189 | 190 | // parts array indicates at which index the next part starts at 191 | while(nparts--) parts.push(s.readSI32()) 192 | 193 | record.parts = parts 194 | 195 | return record 196 | }, 197 | _readPoint: function(record){ 198 | var s = this.stream 199 | 200 | record.x = s.readDouble() 201 | record.y = s.readDouble() 202 | 203 | return record 204 | }, 205 | _readPoints: function(record){ 206 | var s = this.stream, 207 | points = [], 208 | npoints = record.numPoints || (record.numPoints = s.readSI32()) 209 | 210 | while(npoints--) 211 | points.push({ 212 | x: s.readDouble(), 213 | y: s.readDouble() 214 | }) 215 | 216 | record.points = points 217 | 218 | return record 219 | }, 220 | _readMultiPoint: function(record){ 221 | var s = this.stream 222 | 223 | this._readBounds(record) 224 | this._readPoints(record) 225 | 226 | return record 227 | }, 228 | _readPolygon: function(record){ 229 | var s = this.stream 230 | 231 | this._readBounds(record) 232 | this._readParts(record) 233 | this._readPoints(record) 234 | 235 | return record 236 | }, 237 | _readPolyLine: function(record){ 238 | return this._readPolygon(record); 239 | }, 240 | formatIntoGeoJson: function(){ 241 | var bounds = this.header.bounds, 242 | records = this.records, 243 | features = [], 244 | feature, geometry, points, fbounds, gcoords, parts, point, 245 | geojson = {} 246 | 247 | geojson.type = "FeatureCollection" 248 | geojson.bbox = [ 249 | bounds.left, 250 | bounds.bottom, 251 | bounds.right, 252 | bounds.top 253 | ] 254 | geojson.features = features 255 | 256 | for (var r = 0, record; record = records[r]; r++){ 257 | feature = {}, fbounds = record.bounds, points = record.points, parts = record.parts 258 | feature.type = "Feature" 259 | feature.bbox = [ 260 | fbounds.left, 261 | fbounds.bottom, 262 | fbounds.right, 263 | fbounds.top 264 | ] 265 | geometry = feature.geometry = {} 266 | 267 | switch (record.shapeType) { 268 | case "Point": 269 | geometry.type = "Point" 270 | geometry.coordinates = [ 271 | record.points.x, 272 | record.points,y ] 273 | break 274 | case "MultiPoint": 275 | case "PolyLine": 276 | geometry.type = (record.shapeType == "PolyLine" ? "LineString" : "MultiPoint") 277 | gcoords = geometry.coordinates = [] 278 | 279 | for (var p = 0; p < points.length; p++){ 280 | var point = points[p] 281 | gcoords.push([point.x,point.y]) 282 | } 283 | break 284 | case "Polygon": 285 | geometry.type = "Polygon" 286 | gcoords = geometry.coordinates = [] 287 | 288 | for (var pt = 0; pt < parts.length; pt++){ 289 | var partIndex = parts[pt], 290 | part = [], 291 | point 292 | 293 | // partIndex 0 == main poly, partIndex > 0 == holes in poly 294 | for (var p = partIndex; p < (parts[pt+1] || points.length); p++){ 295 | point = points[p] 296 | part.push([point.x,point.y]) 297 | } 298 | gcoords.push(part) 299 | } 300 | break 301 | default: 302 | } 303 | features.push(feature) 304 | } 305 | this.geojson = geojson 306 | 307 | if(this._addDataAfterLoad) this.addDBFDataToGeoJSON(this._addDataAfterLoad); 308 | }, 309 | addDBFDataToGeoJSON: function(dbfData){ 310 | if(!this.geojson) return (this._addDataAfterLoad = dbfData) 311 | 312 | this.dbf = dbfData 313 | 314 | var features = this.geojson.features, 315 | len = features.length, 316 | records = dbfData.records 317 | 318 | while(len--) features[len].properties = records[len] 319 | } 320 | } 321 | 322 | window["Shapefile"] = Shapefile; 323 | })(self) 324 | 325 | -------------------------------------------------------------------------------- /tests/js-shapefile-to-geojson/stream.js: -------------------------------------------------------------------------------- 1 | /* 2 | Stream Reader from Gordon.JS 3 | Copyright (c) 2010 Tobias Schneider 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 13 | all 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 21 | THE SOFTWARE. 22 | */ 23 | 24 | var win = self, 25 | doc = win.document, 26 | fromCharCode = String.fromCharCode, 27 | push = Array.prototype.push, 28 | min = Math.min, 29 | max = Math.max; 30 | 31 | (function(window,undefined){ 32 | 33 | window.Gordon = {}; 34 | 35 | var DEFLATE_CODE_LENGTH_ORDER = [16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15], 36 | DEFLATE_CODE_LENGHT_MAP = [ 37 | [0, 3], [0, 4], [0, 5], [0, 6], [0, 7], [0, 8], [0, 9], [0, 10], [1, 11], [1, 13], [1, 15], [1, 17], 38 | [2, 19], [2, 23], [2, 27], [2, 31], [3, 35], [3, 43], [3, 51], [3, 59], [4, 67], [4, 83], [4, 99], 39 | [4, 115], [5, 131], [5, 163], [5, 195], [5, 227], [0, 258] 40 | ], 41 | DEFLATE_DISTANCE_MAP = [ 42 | [0, 1], [0, 2], [0, 3], [0, 4], [1, 5], [1, 7], [2, 9], [2, 13], [3, 17], [3, 25], [4, 33], [4, 49], 43 | [5, 65], [5, 97], [6, 129], [6, 193], [7, 257], [7, 385], [8, 513], [8, 769], [9, 1025], [9, 1537], 44 | [10, 2049], [10, 3073], [11, 4097], [11, 6145], [12, 8193], [12, 12289], [13, 16385], [13, 24577] 45 | ]; 46 | 47 | Gordon.Stream = function(data){ 48 | var buff = [], 49 | t = this, 50 | i = t.length = data.length; 51 | t.offset = 0; 52 | for(var i = 0; data[i]; i++){ buff.push(fromCharCode(data.charCodeAt(i) & 0xff)); } 53 | t._buffer = buff.join(''); 54 | t._bitBuffer = null; 55 | t._bitOffset = 8; 56 | }; 57 | Gordon.Stream.prototype = { 58 | readByteAt: function(pos){ 59 | return this._buffer.charCodeAt(pos); 60 | }, 61 | 62 | readNumber: function(numBytes, bigEnd){ 63 | var t = this, 64 | val = 0; 65 | if(bigEnd){ 66 | while(numBytes--){ val = (val << 8) | t.readByteAt(t.offset++); } 67 | }else{ 68 | var o = t.offset, 69 | i = o + numBytes; 70 | while(i > o){ val = (val << 8) | t.readByteAt(--i); } 71 | t.offset += numBytes; 72 | } 73 | t.align(); 74 | return val; 75 | }, 76 | 77 | readSNumber: function(numBytes, bigEnd){ 78 | var val = this.readNumber(numBytes, bigEnd), 79 | numBits = numBytes * 8; 80 | if(val >> (numBits - 1)){ val -= Math.pow(2, numBits); } 81 | return val; 82 | }, 83 | 84 | readSI8: function(){ 85 | return this.readSNumber(1); 86 | }, 87 | 88 | readSI16: function(bigEnd){ 89 | return this.readSNumber(2, bigEnd); 90 | }, 91 | 92 | readSI32: function(bigEnd){ 93 | return this.readSNumber(4, bigEnd); 94 | }, 95 | 96 | readUI8: function(){ 97 | return this.readNumber(1); 98 | }, 99 | 100 | readUI16: function(bigEnd){ 101 | return this.readNumber(2, bigEnd); 102 | }, 103 | 104 | readUI24: function(bigEnd){ 105 | return this.readNumber(3, bigEnd); 106 | }, 107 | 108 | readUI32: function(bigEnd){ 109 | return this.readNumber(4, bigEnd); 110 | }, 111 | 112 | readFixed: function(){ 113 | return this._readFixedPoint(32, 16); 114 | }, 115 | 116 | _readFixedPoint: function(numBits, precision){ 117 | return this.readSB(numBits) * Math.pow(2, -precision); 118 | }, 119 | 120 | readFixed8: function(){ 121 | return this._readFixedPoint(16, 8); 122 | }, 123 | 124 | readFloat: function(){ 125 | return this._readFloatingPoint(8, 23); 126 | }, 127 | 128 | _readFloatingPoint: function(numEBits, numSBits){ 129 | var numBits = 1 + numEBits + numSBits, 130 | numBytes = numBits / 8, 131 | t = this, 132 | val = 0.0; 133 | if(numBytes > 4){ 134 | var i = Math.ceil(numBytes / 4); 135 | while(i--){ 136 | var buff = [], 137 | o = t.offset, 138 | j = o + (numBytes >= 4 ? 4 : numBytes % 4); 139 | while(j > o){ 140 | buff.push(t.readByteAt(--j)); 141 | numBytes--; 142 | t.offset++; 143 | } 144 | } 145 | var s = new Gordon.Stream(fromCharCode.apply(String, buff)), 146 | sign = s.readUB(1), 147 | expo = s.readUB(numEBits), 148 | mantis = 0, 149 | i = numSBits; 150 | while(i--){ 151 | if(s.readBool()){ mantis += Math.pow(2, i); } 152 | } 153 | }else{ 154 | var sign = t.readUB(1), 155 | expo = t.readUB(numEBits), 156 | mantis = t.readUB(numSBits); 157 | } 158 | if(sign || expo || mantis){ 159 | var maxExpo = Math.pow(2, numEBits), 160 | bias = ~~((maxExpo - 1) / 2), 161 | scale = Math.pow(2, numSBits), 162 | fract = mantis / scale; 163 | if(bias){ 164 | if(bias < maxExpo){ val = Math.pow(2, expo - bias) * (1 + fract); } 165 | else if(fract){ val = NaN; } 166 | else{ val = Infinity; } 167 | }else if(fract){ val = Math.pow(2, 1 - bias) * fract; } 168 | if(NaN != val && sign){ val *= -1; } 169 | } 170 | return val; 171 | }, 172 | 173 | readFloat16: function(){ 174 | return this._readFloatingPoint(5, 10); 175 | }, 176 | 177 | readDouble: function(){ 178 | return this._readFloatingPoint(11, 52); 179 | }, 180 | 181 | readEncodedU32: function(){ 182 | var val = 0; 183 | for(var i = 0; i < 5; i++){ 184 | var num = this.readByteAt(this._offset++); 185 | val = val | ((num & 0x7f) << (7 * i)); 186 | if(!(num & 0x80)){ break; } 187 | } 188 | return val; 189 | }, 190 | 191 | readSB: function(numBits){ 192 | var val = this.readUB(numBits); 193 | if(val >> (numBits - 1)){ val -= Math.pow(2, numBits); } 194 | return val; 195 | }, 196 | 197 | readUB: function(numBits, lsb){ 198 | var t = this, 199 | val = 0; 200 | for(var i = 0; i < numBits; i++){ 201 | if(8 == t._bitOffset){ 202 | t._bitBuffer = t.readUI8(); 203 | t._bitOffset = 0; 204 | } 205 | if(lsb){ val |= (t._bitBuffer & (0x01 << t._bitOffset++) ? 1 : 0) << i; } 206 | else{ val = (val << 1) | (t._bitBuffer & (0x80 >> t._bitOffset++) ? 1 : 0); } 207 | } 208 | return val; 209 | }, 210 | 211 | readFB: function(numBits){ 212 | return this._readFixedPoint(numBits, 16); 213 | }, 214 | 215 | readString: function(numChars){ 216 | var t = this, 217 | b = t._buffer; 218 | if(undefined != numChars){ 219 | var str = b.substr(t.offset, numChars); 220 | t.offset += numChars; 221 | }else{ 222 | var chars = [], 223 | i = t.length - t.offset; 224 | while(i--){ 225 | var code = t.readByteAt(t.offset++); 226 | if(code){ chars.push(fromCharCode(code)); } 227 | else{ break; } 228 | } 229 | var str = chars.join(''); 230 | } 231 | return str; 232 | }, 233 | 234 | readBool: function(numBits){ 235 | return !!this.readUB(numBits || 1); 236 | }, 237 | 238 | seek: function(offset, absolute){ 239 | var t = this; 240 | t.offset = (absolute ? 0 : t.offset) + offset; 241 | t.align(); 242 | return t; 243 | }, 244 | 245 | align: function(){ 246 | this._bitBuffer = null; 247 | this._bitOffset = 8; 248 | return this; 249 | }, 250 | 251 | readLanguageCode: function(){ 252 | return this.readUI8(); 253 | }, 254 | 255 | readRGB: function(){ 256 | return { 257 | red: this.readUI8(), 258 | green: this.readUI8(), 259 | blue: this.readUI8() 260 | } 261 | }, 262 | 263 | readRGBA: function(){ 264 | var rgba = this.readRGB(); 265 | rgba.alpha = this.readUI8() / 255; 266 | return rgba; 267 | }, 268 | 269 | readARGB: function(){ 270 | var alpha = this.readUI8() / 255, 271 | rgba = this.readRGB(); 272 | rgba.alpha = alpha; 273 | return rgba; 274 | }, 275 | 276 | readRect: function(){ 277 | var t = this; 278 | numBits = t.readUB(5), 279 | rect = { 280 | left: t.readSB(numBits), 281 | right: t.readSB(numBits), 282 | top: t.readSB(numBits), 283 | bottom: t.readSB(numBits) 284 | }; 285 | t.align(); 286 | return rect; 287 | }, 288 | 289 | readMatrix: function(){ 290 | var t = this, 291 | hasScale = t.readBool(); 292 | if(hasScale){ 293 | var numBits = t.readUB(5), 294 | scaleX = t.readFB(numBits), 295 | scaleY = t.readFB(numBits); 296 | }else{ var scaleX = scaleY = 1.0; } 297 | var hasRotation = t.readBool(); 298 | if(hasRotation){ 299 | var numBits = t.readUB(5), 300 | skewX = t.readFB(numBits), 301 | skewY = t.readFB(numBits); 302 | }else{ var skewX = skewY = 0.0; } 303 | var numBits = t.readUB(5); 304 | matrix = { 305 | scaleX: scaleX, scaleY: scaleY, 306 | skewX: skewX, skewY: skewY, 307 | moveX: t.readSB(numBits), moveY: t.readSB(numBits) 308 | }; 309 | t.align(); 310 | return matrix; 311 | }, 312 | 313 | readCxform: function(){ 314 | return this._readCxf(); 315 | }, 316 | 317 | readCxformA: function(){ 318 | return this._readCxf(true); 319 | }, 320 | 321 | _readCxf: function(withAlpha){ 322 | var t = this; 323 | hasAddTerms = t.readBool(), 324 | hasMultTerms = t.readBool(), 325 | numBits = t.readUB(4); 326 | if(hasMultTerms){ 327 | var multR = t.readSB(numBits) / 256, 328 | multG = t.readSB(numBits) / 256, 329 | multB = t.readSB(numBits) / 256, 330 | multA = withAlpha ? t.readSB(numBits) / 256 : 1; 331 | }else{ var multR = multG = multB = multA = 1; } 332 | if(hasAddTerms){ 333 | var addR = t.readSB(numBits), 334 | addG = t.readSB(numBits), 335 | addB = t.readSB(numBits), 336 | addA = withAlpha ? t.readSB(numBits) / 256 : 0; 337 | }else{ var addR = addG = addB = addA = 0; } 338 | var cxform = { 339 | multR: multR, multG: multG, multB: multB, multA: multA, 340 | addR: addR, addG: addG, addB: addB, addA: addA 341 | } 342 | t.align(); 343 | return cxform; 344 | }, 345 | 346 | decompress: function(){ 347 | var t = this, 348 | b = t._buffer, 349 | o = t.offset, 350 | data = b.substr(0, o) + t.unzip(); 351 | t.length = data.length; 352 | t.offset = o; 353 | t._buffer = data; 354 | return t; 355 | }, 356 | 357 | unzip: function uz(raw){ 358 | var t = this, 359 | buff = [], 360 | o = DEFLATE_CODE_LENGTH_ORDER, 361 | m = DEFLATE_CODE_LENGHT_MAP, 362 | d = DEFLATE_DISTANCE_MAP; 363 | t.seek(2); 364 | do{ 365 | var isFinal = t.readUB(1, true), 366 | type = t.readUB(2, true); 367 | if(type){ 368 | if(1 == type){ 369 | var distTable = uz.fixedDistTable, 370 | litTable = uz.fixedLitTable; 371 | if(!distTable){ 372 | var bitLengths = []; 373 | for(var i = 0; i < 32; i++){ bitLengths.push(5); } 374 | distTable = uz.fixedDistTable = buildHuffTable(bitLengths); 375 | } 376 | if(!litTable){ 377 | var bitLengths = []; 378 | for(var i = 0; i <= 143; i++){ bitLengths.push(8); } 379 | for(; i <= 255; i++){ bitLengths.push(9); } 380 | for(; i <= 279; i++){ bitLengths.push(7); } 381 | for(; i <= 287; i++){ bitLengths.push(8); } 382 | litTable = uz.fixedLitTable = buildHuffTable(bitLengths); 383 | } 384 | }else{ 385 | var numLitLengths = t.readUB(5, true) + 257, 386 | numDistLengths = t.readUB(5, true) + 1, 387 | numCodeLenghts = t.readUB(4, true) + 4, 388 | codeLengths = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; 389 | for(var i = 0; i < numCodeLenghts; i++){ codeLengths[o[i]] = t.readUB(3, true); } 390 | var codeTable = buildHuffTable(codeLengths), 391 | litLengths = [], 392 | prevCodeLen = 0, 393 | maxLengths = numLitLengths + numDistLengths; 394 | while(litLengths.length < maxLengths){ 395 | var sym = decodeSymbol(t, codeTable); 396 | switch(sym){ 397 | case 16: 398 | var i = t.readUB(2, true) + 3; 399 | while(i--){ litLengths.push(prevCodeLen); } 400 | break; 401 | case 17: 402 | var i = t.readUB(3, true) + 3; 403 | while(i--){ litLengths.push(0); } 404 | break; 405 | case 18: 406 | var i = t.readUB(7, true) + 11; 407 | while(i--){ litLengths.push(0); } 408 | break; 409 | default: 410 | if(sym <= 15){ 411 | litLengths.push(sym); 412 | prevCodeLen = sym; 413 | } 414 | } 415 | } 416 | var distTable = buildHuffTable(litLengths.splice(numLitLengths, numDistLengths)), 417 | litTable = buildHuffTable(litLengths); 418 | } 419 | do{ 420 | var sym = decodeSymbol(t, litTable); 421 | if(sym < 256){ buff.push(raw ? sym : fromCharCode(sym)); } 422 | else if(sym > 256){ 423 | var lengthMap = m[sym - 257], 424 | len = lengthMap[1] + t.readUB(lengthMap[0], true), 425 | distMap = d[decodeSymbol(t, distTable)], 426 | dist = distMap[1] + t.readUB(distMap[0], true), 427 | i = buff.length - dist; 428 | while(len--){ buff.push(buff[i++]); } 429 | } 430 | }while(256 != sym); 431 | }else{ 432 | t.align(); 433 | var len = t.readUI16(), 434 | nlen = t.readUI16(); 435 | if(raw){ while(len--){ buff.push(t.readUI8()); } } 436 | else{ buff.push(t.readString(len)); } 437 | } 438 | }while(!isFinal); 439 | t.seek(4); 440 | return raw ? buff : buff.join(''); 441 | } 442 | }; 443 | 444 | function buildHuffTable(bitLengths){ 445 | var numLengths = bitLengths.length, 446 | blCount = [], 447 | maxBits = max.apply(Math, bitLengths), 448 | nextCode = [], 449 | code = 0, 450 | table = {}, 451 | i = numLengths; 452 | while(i--){ 453 | var len = bitLengths[i]; 454 | blCount[len] = (blCount[len] || 0) + (len > 0); 455 | } 456 | for(var i = 1; i <= maxBits; i++){ 457 | var len = i - 1; 458 | if(undefined == blCount[len]){ blCount[len] = 0; } 459 | code = (code + blCount[i - 1]) << 1; 460 | nextCode[i] = code; 461 | } 462 | for(var i = 0; i < numLengths; i++){ 463 | var len = bitLengths[i]; 464 | if(len){ 465 | table[nextCode[len]] = { 466 | length: len, 467 | symbol: i 468 | }; 469 | nextCode[len]++; 470 | } 471 | } 472 | return table; 473 | } 474 | 475 | function decodeSymbol(s, table) { 476 | var code = 0, 477 | len = 0; 478 | while(true){ 479 | code = (code << 1) | s.readUB(1, true); 480 | len++; 481 | var entry = table[code]; 482 | if(undefined != entry && entry.length == len){ return entry.symbol } 483 | } 484 | } 485 | })(this); 486 | 487 | -------------------------------------------------------------------------------- /tests/js-shapefile-to-geojson/testdata/world.dbf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simogeo/geostats/be8159073de1863bc4b52b05de3f63b2fc326ab6/tests/js-shapefile-to-geojson/testdata/world.dbf -------------------------------------------------------------------------------- /tests/js-shapefile-to-geojson/testdata/world.shp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simogeo/geostats/be8159073de1863bc4b52b05de3f63b2fc326ab6/tests/js-shapefile-to-geojson/testdata/world.shp -------------------------------------------------------------------------------- /tests/openlayers_sample.js: -------------------------------------------------------------------------------- 1 | // get user defined class 2 | // if equals value 3 | // handle both numeric and string (unique values classification) 4 | function getClass(val, a) { 5 | 6 | var separator = ' - ' 7 | 8 | // return 2; 9 | for(var i= 0; i < a.length; i++) { 10 | // all classification except uniqueValues 11 | if(a[i].indexOf(separator) != -1) { 12 | var item = a[i].split(separator); 13 | if(val <= parseFloat(item[1])) {return i;} 14 | } else { 15 | // uniqueValues classification 16 | if(val == a[i]) { 17 | return i; 18 | } 19 | } 20 | } 21 | } 22 | 23 | // Function called from body tag 24 | function init(color_x, class_x) { 25 | 26 | var lon = 6.3; 27 | var lat = 48.73; 28 | var zoom = 4; 29 | 30 | OpenLayers.ImgPath = "http://js.mapbox.com/theme/dark/"; 31 | 32 | var context_x = { 33 | getColour: function(feature) { 34 | color = color_x; 35 | return color[getClass(feature.attributes["Pop_2006"], class_x)]; 36 | } 37 | }; 38 | 39 | var template = { 40 | fillOpacity: 0.9, 41 | strokeColor: "#ffffff", 42 | strokeWidth: 1, 43 | fillColor: "${getColour}" 44 | }; 45 | 46 | // Style 47 | var style_x = new OpenLayers.Style(template, {context: context_x}); 48 | var styleMap_x = new OpenLayers.StyleMap({'default': style_x}); 49 | 50 | // options de la carte 51 | var options = { 52 | scales: [10000000, 5000000, 2500000, 1000000, 500000, 250000], 53 | controls: [], // Remove all controls 54 | // allOverlays: true 55 | }; 56 | 57 | // Create a new map with options defined above 58 | map = new OpenLayers.Map( 'map', options ); 59 | 60 | 61 | // Create polygon layer as vector features 62 | // http://dev.openlayers.org/docs/files/OpenLayers/Layer/Vector-js.html 63 | 64 | layer_x = new OpenLayers.Layer.GML("choropleth", "tests/test.json", 65 | { format: OpenLayers.Format.GeoJSON, 66 | styleMap: styleMap_x, 67 | isBaseLayer: true, 68 | projection: new OpenLayers.Projection("EPSG:4326") 69 | }); 70 | 71 | map.addLayers([layer_x]); 72 | map.setCenter(new OpenLayers.LonLat(lon, lat), zoom); 73 | 74 | // Add map controls: 75 | // http://dev.openlayers.org/docs/files/OpenLayers/Control-js.html 76 | map.addControl(new OpenLayers.Control.MousePosition()); 77 | map.addControl(new OpenLayers.Control.Navigation()); 78 | map.addControl(new OpenLayers.Control.PanZoomBar()); 79 | map.addControl(new OpenLayers.Control.ScaleLine({geodesic: true, maxWidth: 200, bottomOutUnits: "", bottomInUnits: ""})); 80 | 81 | } 82 | 83 | //Function called from body tag 84 | function initChromaSample(color_x, class_x) { 85 | 86 | var lon = 6.3; 87 | var lat = 48.73; 88 | var zoom = 4; 89 | 90 | OpenLayers.ImgPath = "http://js.mapbox.com/theme/dark/"; 91 | 92 | var context_x = { 93 | getColour: function(feature) { 94 | color = color_x; 95 | return color[getClass(feature.attributes["Pop_2006"], class_x)]; 96 | } 97 | }; 98 | 99 | var template = { 100 | fillOpacity: 0.9, 101 | strokeColor: "#ffffff", 102 | strokeWidth: 1, 103 | fillColor: "${getColour}" 104 | }; 105 | 106 | // Style 107 | var style_x = new OpenLayers.Style(template, {context: context_x}); 108 | var styleMap_x = new OpenLayers.StyleMap({'default': style_x}); 109 | 110 | // options de la carte 111 | var options = { 112 | scales: [10000000, 5000000, 2500000, 1000000, 500000, 250000], 113 | controls: [], // Remove all controls 114 | // allOverlays: true 115 | }; 116 | 117 | // Create a new map with options defined above 118 | map = new OpenLayers.Map( 'map3', options ); 119 | 120 | 121 | // Create polygon layer as vector features 122 | // http://dev.openlayers.org/docs/files/OpenLayers/Layer/Vector-js.html 123 | 124 | layer_x = new OpenLayers.Layer.GML("choropleth", "tests/test.json", 125 | { format: OpenLayers.Format.GeoJSON, 126 | styleMap: styleMap_x, 127 | isBaseLayer: true, 128 | projection: new OpenLayers.Projection("EPSG:4326") 129 | }); 130 | 131 | map.addLayers([layer_x]); 132 | map.setCenter(new OpenLayers.LonLat(lon, lat), zoom); 133 | 134 | // Add map controls: 135 | // http://dev.openlayers.org/docs/files/OpenLayers/Control-js.html 136 | map.addControl(new OpenLayers.Control.MousePosition()); 137 | map.addControl(new OpenLayers.Control.Navigation()); 138 | map.addControl(new OpenLayers.Control.PanZoomBar()); 139 | map.addControl(new OpenLayers.Control.ScaleLine({geodesic: true, maxWidth: 200, bottomOutUnits: "", bottomInUnits: ""})); 140 | 141 | } 142 | 143 | //Function called from body tag 144 | function initUniqueValues(color_x, class_x) { 145 | 146 | var lon = 6.3; 147 | var lat = 48.73; 148 | var zoom = 4; 149 | 150 | OpenLayers.ImgPath = "http://js.mapbox.com/theme/dark/"; 151 | 152 | var context_x = { 153 | getColour: function(feature) { 154 | color = color_x; 155 | return color[getClass(feature.attributes["value"], class_x)]; 156 | } 157 | }; 158 | 159 | var template = { 160 | fillOpacity: 0.9, 161 | strokeColor: "#ffffff", 162 | strokeWidth: 1, 163 | fillColor: "${getColour}" 164 | }; 165 | 166 | // Style 167 | var style_x = new OpenLayers.Style(template, {context: context_x}); 168 | var styleMap_x = new OpenLayers.StyleMap({'default': style_x}); 169 | 170 | // options de la carte 171 | var options = { 172 | scales: [10000000, 5000000, 2500000, 1000000, 500000, 250000], 173 | controls: [], // Remove all controls 174 | // allOverlays: true 175 | }; 176 | 177 | // Create a new map with options defined above 178 | map2 = new OpenLayers.Map( 'map2', options ); 179 | 180 | 181 | // Create polygon layer as vector features 182 | // http://dev.openlayers.org/docs/files/OpenLayers/Layer/Vector-js.html 183 | 184 | layer_x = new OpenLayers.Layer.GML("choropleth", "tests/test.json", 185 | { format: OpenLayers.Format.GeoJSON, 186 | styleMap: styleMap_x, 187 | isBaseLayer: true, 188 | projection: new OpenLayers.Projection("EPSG:4326") 189 | }); 190 | 191 | map2.addLayers([layer_x]); 192 | map2.setCenter(new OpenLayers.LonLat(lon, lat), zoom); 193 | 194 | // Add map controls: 195 | // http://dev.openlayers.org/docs/files/OpenLayers/Control-js.html 196 | map2.addControl(new OpenLayers.Control.MousePosition()); 197 | map2.addControl(new OpenLayers.Control.Navigation()); 198 | map2.addControl(new OpenLayers.Control.PanZoomBar()); 199 | map2.addControl(new OpenLayers.Control.ScaleLine({geodesic: true, maxWidth: 200, bottomOutUnits: "", bottomInUnits: ""})); 200 | 201 | } -------------------------------------------------------------------------------- /tests/qunit_test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | QUnit for geostats 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 36 | 37 | --------------------------------------------------------------------------------