UNPKG

5.8 kBJavaScriptView Raw
1var area = require('turf-area')
2var clip = require('geojson-clip-polygon')
3var xtend = require('xtend')
4var uniq = require('uniq')
5var through = require('through2')
6
7/**
8 * Aggregate properties of GeoJSON polygon features.
9 *
10 * @param {FeatureCollection<Polygon>|Array} data - The polygons to aggregate.
11 * @param {Object} aggregations - The aggregations as key-value pairs, where the key is the name of the resulting property, and the value is the aggregation function, with signature (accumulator, clippedFeature, groupingFeature) => accumulator
12 *
13 * @return {Object} A properties object with the aggregated property values
14 */
15/**
16 * Aggregate properties of streaming GeoJSON polygon features.
17 *
18 * @param {Object} aggregations - The aggregations as key-value pairs, where the key is the name of the resulting property, and the value is the aggregation function, with signature (accumulator, clippedFeature, groupingFeature) => accumulator
19 *
20 * @return {Object} A transform stream reading GeoJSON feature objects and, writing, at the end, a properties object with the aggregated property values
21 */
22/**
23 * Aggregate properties of GeoJSON polygon features, grouped by another set of
24 * polygons.
25 *
26 * @param {FeatureCollection<Polygon>|Array} groups - The polygons by which to group the aggregations.
27 * @param {FeatureCollection<Polygon>|Array} data - The polygons to aggregate.
28 * @param {Object} aggregations - The aggregations as key-value pairs, where the key is the name of the resulting property, and the value is the aggregation function, with signature (accumulator, clippedFeature, groupingFeature) => accumulator
29 *
30 * @return {FeatureCollection<Polygon>} A set of polygons whose geometries are identical to `groups`, but
31 * with properties resulting from the aggregations. Existing properties on features in `groups` are
32 * copied (shallowly), but aggregation results will override if they have the same name.
33 */
34module.exports = function (groups, data, aggregations) {
35 if (!data) {
36 aggregations = groups
37 return aggregateStream(aggregations)
38 }
39
40 if (!aggregations) {
41 aggregations = data
42 data = groups
43 return aggregateAll(data, aggregations)
44 }
45
46 groups = Array.isArray(groups) ? groups : groups.features
47 data = Array.isArray(data) ? data : data.features
48
49 return {
50 type: 'FeatureCollection',
51 features: groups.map(aggregate)
52 }
53
54 function aggregate (group) {
55 var properties = xtend({}, group.properties)
56 data
57 .map(function (f) { return clip(group, f, { threshold: 0 }) })
58 .filter(function (clipped) { return !!clipped })
59 .forEach(function (clipped) {
60 for (var prop in aggregations) {
61 properties[prop] = aggregations[prop](properties[prop], clipped)
62 }
63 })
64
65 for (var prop in aggregations) {
66 if (typeof aggregations[prop].finish === 'function') {
67 properties[prop] = aggregations[prop].finish(properties[prop], group)
68 }
69 }
70
71 return {
72 type: group.type,
73 properties: properties,
74 geometry: group.geometry
75 }
76 }
77}
78
79function aggregateAll (features, aggregations) {
80 if (!Array.isArray(features)) { features = features.features }
81 var properties = {}
82 for (var prop in aggregations) {
83 for (var i = features.length - 1; i >= 0; i--) {
84 properties[prop] = aggregations[prop](properties[prop], features[i])
85 }
86
87 if (typeof aggregations[prop].finish === 'function') {
88 properties[prop] = aggregations[prop].finish(properties[prop])
89 }
90 }
91 return properties
92}
93
94function aggregateStream (aggregations) {
95 var properties = {}
96 return through.obj(function write (feature, enc, next) {
97 for (var prop in aggregations) {
98 properties[prop] = aggregations[prop](properties[prop], feature)
99 }
100 next()
101 }, function end () {
102 for (var prop in aggregations) {
103 if (typeof aggregations[prop].finish === 'function') {
104 properties[prop] = aggregations[prop].finish(properties[prop])
105 }
106 }
107
108 this.push(properties)
109 this.push(null)
110 })
111}
112
113module.exports.count = function () {
114 return function (c) { return (c || 0) + 1 }
115}
116
117/**
118 * Return an aggregation that collects the unique, primitive values of the given
119 * property into a (stringified) array. If the property value is a stringified
120 * array, it is unpacked--i.e., the array contents are collected rather than the
121 * array itself.
122 */
123module.exports.union = function (property) {
124 function collect (memo, feature) {
125 memo = (memo || [])
126 if (!(property in feature.properties)) { return memo }
127
128 // this is safe bc JSON.parse is a noop if the value is already a primitive
129 var value = JSON.parse(feature.properties[property])
130
131 if (Array.isArray(value)) {
132 memo.push.apply(memo, value)
133 } else {
134 memo.push(value)
135 }
136 return memo
137 }
138
139 collect.finish = function (memo) {
140 return memo ? JSON.stringify(uniq(memo, false, false)) : '[]'
141 }
142
143 return collect
144}
145
146module.exports.totalArea = function () {
147 return function (a, feature) {
148 return (a || 0) + area(feature)
149 }
150}
151
152module.exports.sum = function (property) {
153 return function (s, feature) {
154 return (s || 0) + (feature.properties[property] || 0)
155 }
156}
157
158module.exports.areaWeightedSum = function (property) {
159 return function (s, feature) {
160 return (s || 0) + area(feature) * (feature.properties[property] || 0)
161 }
162}
163
164module.exports.areaWeightedMean = function (property) {
165 var ws = module.exports.areaWeightedSum(property)
166 var ta = module.exports.totalArea()
167
168 function weightedMean (memo, feature) {
169 memo = memo || {}
170 memo.sum = ws(memo.sum, feature)
171 memo.area = ta(memo.area, feature)
172 return memo
173 }
174
175 weightedMean.finish = function (memo) {
176 return memo ? memo.sum / memo.area : 0
177 }
178
179 return weightedMean
180}