index.js 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. import center from '@turf/center';
  2. import { GeoJSONReader, BufferOp, GeoJSONWriter } from 'turf-jsts';
  3. import { featureEach, geomEach } from '@turf/meta';
  4. import { geoAzimuthalEquidistant } from 'd3-geo';
  5. import { featureCollection, earthRadius, radiansToLength, lengthToRadians, feature } from '@turf/helpers';
  6. /**
  7. * Calculates a buffer for input features for a given radius. Units supported are miles, kilometers, and degrees.
  8. *
  9. * When using a negative radius, the resulting geometry may be invalid if
  10. * it's too small compared to the radius magnitude. If the input is a
  11. * FeatureCollection, only valid members will be returned in the output
  12. * FeatureCollection - i.e., the output collection may have fewer members than
  13. * the input, or even be empty.
  14. *
  15. * @name buffer
  16. * @param {FeatureCollection|Geometry|Feature<any>} geojson input to be buffered
  17. * @param {number} radius distance to draw the buffer (negative values are allowed)
  18. * @param {Object} [options={}] Optional parameters
  19. * @param {string} [options.units="kilometers"] any of the options supported by turf units
  20. * @param {number} [options.steps=8] number of steps
  21. * @returns {FeatureCollection|Feature<Polygon|MultiPolygon>|undefined} buffered features
  22. * @example
  23. * var point = turf.point([-90.548630, 14.616599]);
  24. * var buffered = turf.buffer(point, 500, {units: 'miles'});
  25. *
  26. * //addToMap
  27. * var addToMap = [point, buffered]
  28. */
  29. function buffer(geojson, radius, options) {
  30. // Optional params
  31. options = options || {};
  32. // use user supplied options or default values
  33. var units = options.units || "kilometers";
  34. var steps = options.steps || 8;
  35. // validation
  36. if (!geojson) throw new Error("geojson is required");
  37. if (typeof options !== "object") throw new Error("options must be an object");
  38. if (typeof steps !== "number") throw new Error("steps must be an number");
  39. // Allow negative buffers ("erosion") or zero-sized buffers ("repair geometry")
  40. if (radius === undefined) throw new Error("radius is required");
  41. if (steps <= 0) throw new Error("steps must be greater than 0");
  42. var results = [];
  43. switch (geojson.type) {
  44. case "GeometryCollection":
  45. geomEach(geojson, function (geometry) {
  46. var buffered = bufferFeature(geometry, radius, units, steps);
  47. if (buffered) results.push(buffered);
  48. });
  49. return featureCollection(results);
  50. case "FeatureCollection":
  51. featureEach(geojson, function (feature) {
  52. var multiBuffered = bufferFeature(feature, radius, units, steps);
  53. if (multiBuffered) {
  54. featureEach(multiBuffered, function (buffered) {
  55. if (buffered) results.push(buffered);
  56. });
  57. }
  58. });
  59. return featureCollection(results);
  60. }
  61. return bufferFeature(geojson, radius, units, steps);
  62. }
  63. /**
  64. * Buffer single Feature/Geometry
  65. *
  66. * @private
  67. * @param {Feature<any>} geojson input to be buffered
  68. * @param {number} radius distance to draw the buffer
  69. * @param {string} [units='kilometers'] any of the options supported by turf units
  70. * @param {number} [steps=8] number of steps
  71. * @returns {Feature<Polygon|MultiPolygon>} buffered feature
  72. */
  73. function bufferFeature(geojson, radius, units, steps) {
  74. var properties = geojson.properties || {};
  75. var geometry = geojson.type === "Feature" ? geojson.geometry : geojson;
  76. // Geometry Types faster than jsts
  77. if (geometry.type === "GeometryCollection") {
  78. var results = [];
  79. geomEach(geojson, function (geometry) {
  80. var buffered = bufferFeature(geometry, radius, units, steps);
  81. if (buffered) results.push(buffered);
  82. });
  83. return featureCollection(results);
  84. }
  85. // Project GeoJSON to Azimuthal Equidistant projection (convert to Meters)
  86. var projection = defineProjection(geometry);
  87. var projected = {
  88. type: geometry.type,
  89. coordinates: projectCoords(geometry.coordinates, projection),
  90. };
  91. // JSTS buffer operation
  92. var reader = new GeoJSONReader();
  93. var geom = reader.read(projected);
  94. var distance = radiansToLength(lengthToRadians(radius, units), "meters");
  95. var buffered = BufferOp.bufferOp(geom, distance, steps);
  96. var writer = new GeoJSONWriter();
  97. buffered = writer.write(buffered);
  98. // Detect if empty geometries
  99. if (coordsIsNaN(buffered.coordinates)) return undefined;
  100. // Unproject coordinates (convert to Degrees)
  101. var result = {
  102. type: buffered.type,
  103. coordinates: unprojectCoords(buffered.coordinates, projection),
  104. };
  105. return feature(result, properties);
  106. }
  107. /**
  108. * Coordinates isNaN
  109. *
  110. * @private
  111. * @param {Array<any>} coords GeoJSON Coordinates
  112. * @returns {boolean} if NaN exists
  113. */
  114. function coordsIsNaN(coords) {
  115. if (Array.isArray(coords[0])) return coordsIsNaN(coords[0]);
  116. return isNaN(coords[0]);
  117. }
  118. /**
  119. * Project coordinates to projection
  120. *
  121. * @private
  122. * @param {Array<any>} coords to project
  123. * @param {GeoProjection} proj D3 Geo Projection
  124. * @returns {Array<any>} projected coordinates
  125. */
  126. function projectCoords(coords, proj) {
  127. if (typeof coords[0] !== "object") return proj(coords);
  128. return coords.map(function (coord) {
  129. return projectCoords(coord, proj);
  130. });
  131. }
  132. /**
  133. * Un-Project coordinates to projection
  134. *
  135. * @private
  136. * @param {Array<any>} coords to un-project
  137. * @param {GeoProjection} proj D3 Geo Projection
  138. * @returns {Array<any>} un-projected coordinates
  139. */
  140. function unprojectCoords(coords, proj) {
  141. if (typeof coords[0] !== "object") return proj.invert(coords);
  142. return coords.map(function (coord) {
  143. return unprojectCoords(coord, proj);
  144. });
  145. }
  146. /**
  147. * Define Azimuthal Equidistant projection
  148. *
  149. * @private
  150. * @param {Geometry|Feature<any>} geojson Base projection on center of GeoJSON
  151. * @returns {GeoProjection} D3 Geo Azimuthal Equidistant Projection
  152. */
  153. function defineProjection(geojson) {
  154. var coords = center(geojson).geometry.coordinates;
  155. var rotation = [-coords[0], -coords[1]];
  156. return geoAzimuthalEquidistant().rotate(rotation).scale(earthRadius);
  157. }
  158. export default buffer;