geometry.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384
  1. module.exports = Geometry;
  2. var Types = require('./types');
  3. var Point = require('./point');
  4. var LineString = require('./linestring');
  5. var Polygon = require('./polygon');
  6. var MultiPoint = require('./multipoint');
  7. var MultiLineString = require('./multilinestring');
  8. var MultiPolygon = require('./multipolygon');
  9. var GeometryCollection = require('./geometrycollection');
  10. var BinaryReader = require('./binaryreader');
  11. var BinaryWriter = require('./binarywriter');
  12. var WktParser = require('./wktparser');
  13. var ZigZag = require('./zigzag.js');
  14. function Geometry() {
  15. this.srid = undefined;
  16. this.hasZ = false;
  17. this.hasM = false;
  18. }
  19. Geometry.parse = function (value, options) {
  20. var valueType = typeof value;
  21. if (valueType === 'string' || value instanceof WktParser)
  22. return Geometry._parseWkt(value);
  23. else if (Buffer.isBuffer(value) || value instanceof BinaryReader)
  24. return Geometry._parseWkb(value, options);
  25. else
  26. throw new Error('first argument must be a string or Buffer');
  27. };
  28. Geometry._parseWkt = function (value) {
  29. var wktParser,
  30. srid;
  31. if (value instanceof WktParser)
  32. wktParser = value;
  33. else
  34. wktParser = new WktParser(value);
  35. var match = wktParser.matchRegex([/^SRID=(\d+);/]);
  36. if (match)
  37. srid = parseInt(match[1], 10);
  38. var geometryType = wktParser.matchType();
  39. var dimension = wktParser.matchDimension();
  40. var options = {
  41. srid: srid,
  42. hasZ: dimension.hasZ,
  43. hasM: dimension.hasM
  44. };
  45. switch (geometryType) {
  46. case Types.wkt.Point:
  47. return Point._parseWkt(wktParser, options);
  48. case Types.wkt.LineString:
  49. return LineString._parseWkt(wktParser, options);
  50. case Types.wkt.Polygon:
  51. return Polygon._parseWkt(wktParser, options);
  52. case Types.wkt.MultiPoint:
  53. return MultiPoint._parseWkt(wktParser, options);
  54. case Types.wkt.MultiLineString:
  55. return MultiLineString._parseWkt(wktParser, options);
  56. case Types.wkt.MultiPolygon:
  57. return MultiPolygon._parseWkt(wktParser, options);
  58. case Types.wkt.GeometryCollection:
  59. return GeometryCollection._parseWkt(wktParser, options);
  60. }
  61. };
  62. Geometry._parseWkb = function (value, parentOptions) {
  63. var binaryReader,
  64. wkbType,
  65. geometryType,
  66. options = {};
  67. if (value instanceof BinaryReader)
  68. binaryReader = value;
  69. else
  70. binaryReader = new BinaryReader(value);
  71. binaryReader.isBigEndian = !binaryReader.readInt8();
  72. wkbType = binaryReader.readUInt32();
  73. options.hasSrid = (wkbType & 0x20000000) === 0x20000000;
  74. options.isEwkb = (wkbType & 0x20000000) || (wkbType & 0x40000000) || (wkbType & 0x80000000);
  75. if (options.hasSrid)
  76. options.srid = binaryReader.readUInt32();
  77. options.hasZ = false;
  78. options.hasM = false;
  79. if (!options.isEwkb && (!parentOptions || !parentOptions.isEwkb)) {
  80. if (wkbType >= 1000 && wkbType < 2000) {
  81. options.hasZ = true;
  82. geometryType = wkbType - 1000;
  83. }
  84. else if (wkbType >= 2000 && wkbType < 3000) {
  85. options.hasM = true;
  86. geometryType = wkbType - 2000;
  87. }
  88. else if (wkbType >= 3000 && wkbType < 4000) {
  89. options.hasZ = true;
  90. options.hasM = true;
  91. geometryType = wkbType - 3000;
  92. }
  93. else {
  94. geometryType = wkbType;
  95. }
  96. }
  97. else {
  98. if (wkbType & 0x80000000)
  99. options.hasZ = true;
  100. if (wkbType & 0x40000000)
  101. options.hasM = true;
  102. geometryType = wkbType & 0xF;
  103. }
  104. switch (geometryType) {
  105. case Types.wkb.Point:
  106. return Point._parseWkb(binaryReader, options);
  107. case Types.wkb.LineString:
  108. return LineString._parseWkb(binaryReader, options);
  109. case Types.wkb.Polygon:
  110. return Polygon._parseWkb(binaryReader, options);
  111. case Types.wkb.MultiPoint:
  112. return MultiPoint._parseWkb(binaryReader, options);
  113. case Types.wkb.MultiLineString:
  114. return MultiLineString._parseWkb(binaryReader, options);
  115. case Types.wkb.MultiPolygon:
  116. return MultiPolygon._parseWkb(binaryReader, options);
  117. case Types.wkb.GeometryCollection:
  118. return GeometryCollection._parseWkb(binaryReader, options);
  119. default:
  120. throw new Error('GeometryType ' + geometryType + ' not supported');
  121. }
  122. };
  123. Geometry.parseTwkb = function (value) {
  124. var binaryReader,
  125. options = {};
  126. if (value instanceof BinaryReader)
  127. binaryReader = value;
  128. else
  129. binaryReader = new BinaryReader(value);
  130. var type = binaryReader.readUInt8();
  131. var metadataHeader = binaryReader.readUInt8();
  132. var geometryType = type & 0x0F;
  133. options.precision = ZigZag.decode(type >> 4);
  134. options.precisionFactor = Math.pow(10, options.precision);
  135. options.hasBoundingBox = metadataHeader >> 0 & 1;
  136. options.hasSizeAttribute = metadataHeader >> 1 & 1;
  137. options.hasIdList = metadataHeader >> 2 & 1;
  138. options.hasExtendedPrecision = metadataHeader >> 3 & 1;
  139. options.isEmpty = metadataHeader >> 4 & 1;
  140. if (options.hasExtendedPrecision) {
  141. var extendedPrecision = binaryReader.readUInt8();
  142. options.hasZ = (extendedPrecision & 0x01) === 0x01;
  143. options.hasM = (extendedPrecision & 0x02) === 0x02;
  144. options.zPrecision = ZigZag.decode((extendedPrecision & 0x1C) >> 2);
  145. options.zPrecisionFactor = Math.pow(10, options.zPrecision);
  146. options.mPrecision = ZigZag.decode((extendedPrecision & 0xE0) >> 5);
  147. options.mPrecisionFactor = Math.pow(10, options.mPrecision);
  148. }
  149. else {
  150. options.hasZ = false;
  151. options.hasM = false;
  152. }
  153. if (options.hasSizeAttribute)
  154. binaryReader.readVarInt();
  155. if (options.hasBoundingBox) {
  156. var dimensions = 2;
  157. if (options.hasZ)
  158. dimensions++;
  159. if (options.hasM)
  160. dimensions++;
  161. for (var i = 0; i < dimensions; i++) {
  162. binaryReader.readVarInt();
  163. binaryReader.readVarInt();
  164. }
  165. }
  166. switch (geometryType) {
  167. case Types.wkb.Point:
  168. return Point._parseTwkb(binaryReader, options);
  169. case Types.wkb.LineString:
  170. return LineString._parseTwkb(binaryReader, options);
  171. case Types.wkb.Polygon:
  172. return Polygon._parseTwkb(binaryReader, options);
  173. case Types.wkb.MultiPoint:
  174. return MultiPoint._parseTwkb(binaryReader, options);
  175. case Types.wkb.MultiLineString:
  176. return MultiLineString._parseTwkb(binaryReader, options);
  177. case Types.wkb.MultiPolygon:
  178. return MultiPolygon._parseTwkb(binaryReader, options);
  179. case Types.wkb.GeometryCollection:
  180. return GeometryCollection._parseTwkb(binaryReader, options);
  181. default:
  182. throw new Error('GeometryType ' + geometryType + ' not supported');
  183. }
  184. };
  185. Geometry.parseGeoJSON = function (value) {
  186. return Geometry._parseGeoJSON(value);
  187. };
  188. Geometry._parseGeoJSON = function (value, isSubGeometry) {
  189. var geometry;
  190. switch (value.type) {
  191. case Types.geoJSON.Point:
  192. geometry = Point._parseGeoJSON(value); break;
  193. case Types.geoJSON.LineString:
  194. geometry = LineString._parseGeoJSON(value); break;
  195. case Types.geoJSON.Polygon:
  196. geometry = Polygon._parseGeoJSON(value); break;
  197. case Types.geoJSON.MultiPoint:
  198. geometry = MultiPoint._parseGeoJSON(value); break;
  199. case Types.geoJSON.MultiLineString:
  200. geometry = MultiLineString._parseGeoJSON(value); break;
  201. case Types.geoJSON.MultiPolygon:
  202. geometry = MultiPolygon._parseGeoJSON(value); break;
  203. case Types.geoJSON.GeometryCollection:
  204. geometry = GeometryCollection._parseGeoJSON(value); break;
  205. default:
  206. throw new Error('GeometryType ' + value.type + ' not supported');
  207. }
  208. if (value.crs && value.crs.type && value.crs.type === 'name' && value.crs.properties && value.crs.properties.name) {
  209. var crs = value.crs.properties.name;
  210. if (crs.indexOf('EPSG:') === 0)
  211. geometry.srid = parseInt(crs.substring(5));
  212. else if (crs.indexOf('urn:ogc:def:crs:EPSG::') === 0)
  213. geometry.srid = parseInt(crs.substring(22));
  214. else
  215. throw new Error('Unsupported crs: ' + crs);
  216. }
  217. else if (!isSubGeometry) {
  218. geometry.srid = 4326;
  219. }
  220. return geometry;
  221. };
  222. Geometry.prototype.toEwkt = function () {
  223. return 'SRID=' + this.srid + ';' + this.toWkt();
  224. };
  225. Geometry.prototype.toEwkb = function () {
  226. var ewkb = new BinaryWriter(this._getWkbSize() + 4);
  227. var wkb = this.toWkb();
  228. ewkb.writeInt8(1);
  229. ewkb.writeUInt32LE((wkb.slice(1, 5).readUInt32LE(0) | 0x20000000) >>> 0, true);
  230. ewkb.writeUInt32LE(this.srid);
  231. ewkb.writeBuffer(wkb.slice(5));
  232. return ewkb.buffer;
  233. };
  234. Geometry.prototype._getWktType = function (wktType, isEmpty) {
  235. var wkt = wktType;
  236. if (this.hasZ && this.hasM)
  237. wkt += ' ZM ';
  238. else if (this.hasZ)
  239. wkt += ' Z ';
  240. else if (this.hasM)
  241. wkt += ' M ';
  242. if (isEmpty && !this.hasZ && !this.hasM)
  243. wkt += ' ';
  244. if (isEmpty)
  245. wkt += 'EMPTY';
  246. return wkt;
  247. };
  248. Geometry.prototype._getWktCoordinate = function (point) {
  249. var coordinates = point.x + ' ' + point.y;
  250. if (this.hasZ)
  251. coordinates += ' ' + point.z;
  252. if (this.hasM)
  253. coordinates += ' ' + point.m;
  254. return coordinates;
  255. };
  256. Geometry.prototype._writeWkbType = function (wkb, geometryType, parentOptions) {
  257. var dimensionType = 0;
  258. if (typeof this.srid === 'undefined' && (!parentOptions || typeof parentOptions.srid === 'undefined')) {
  259. if (this.hasZ && this.hasM)
  260. dimensionType += 3000;
  261. else if (this.hasZ)
  262. dimensionType += 1000;
  263. else if (this.hasM)
  264. dimensionType += 2000;
  265. }
  266. else {
  267. if (this.hasZ)
  268. dimensionType |= 0x80000000;
  269. if (this.hasM)
  270. dimensionType |= 0x40000000;
  271. }
  272. wkb.writeUInt32LE((dimensionType + geometryType) >>> 0, true);
  273. };
  274. Geometry.getTwkbPrecision = function (xyPrecision, zPrecision, mPrecision) {
  275. return {
  276. xy: xyPrecision,
  277. z: zPrecision,
  278. m: mPrecision,
  279. xyFactor: Math.pow(10, xyPrecision),
  280. zFactor: Math.pow(10, zPrecision),
  281. mFactor: Math.pow(10, mPrecision)
  282. };
  283. };
  284. Geometry.prototype._writeTwkbHeader = function (twkb, geometryType, precision, isEmpty) {
  285. var type = (ZigZag.encode(precision.xy) << 4) + geometryType;
  286. var metadataHeader = (this.hasZ || this.hasM) << 3;
  287. metadataHeader += isEmpty << 4;
  288. twkb.writeUInt8(type);
  289. twkb.writeUInt8(metadataHeader);
  290. if (this.hasZ || this.hasM) {
  291. var extendedPrecision = 0;
  292. if (this.hasZ)
  293. extendedPrecision |= 0x1;
  294. if (this.hasM)
  295. extendedPrecision |= 0x2;
  296. twkb.writeUInt8(extendedPrecision);
  297. }
  298. };
  299. Geometry.prototype.toGeoJSON = function (options) {
  300. var geoJSON = {};
  301. if (this.srid) {
  302. if (options) {
  303. if (options.shortCrs) {
  304. geoJSON.crs = {
  305. type: 'name',
  306. properties: {
  307. name: 'EPSG:' + this.srid
  308. }
  309. };
  310. }
  311. else if (options.longCrs) {
  312. geoJSON.crs = {
  313. type: 'name',
  314. properties: {
  315. name: 'urn:ogc:def:crs:EPSG::' + this.srid
  316. }
  317. };
  318. }
  319. }
  320. }
  321. return geoJSON;
  322. };