├── 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 |
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 |
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 |
--------------------------------------------------------------------------------