{"id":13426587,"url":"https://github.com/koopjs/FeatureServer","last_synced_at":"2025-03-15T21:31:29.195Z","repository":{"id":8486674,"uuid":"58579794","full_name":"koopjs/FeatureServer","owner":"koopjs","description":"An open source Geoservices Implementation (deprecated)","archived":false,"fork":false,"pushed_at":"2023-07-13T12:06:37.000Z","size":1003,"stargazers_count":104,"open_issues_count":27,"forks_count":32,"subscribers_count":18,"default_branch":"master","last_synced_at":"2024-10-12T09:36:53.834Z","etag":null,"topics":["arcgis","deprecated","geojson","geoservices","gis","javascript","server"],"latest_commit_sha":null,"homepage":"https://geoservices.github.io","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/koopjs.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2016-05-11T20:44:26.000Z","updated_at":"2024-08-21T00:47:39.000Z","dependencies_parsed_at":"2023-02-13T17:25:17.355Z","dependency_job_id":"d2b0051a-c043-4930-bd28-758c3c2f9457","html_url":"https://github.com/koopjs/FeatureServer","commit_stats":{"total_commits":383,"total_committers":22,"mean_commits":17.40909090909091,"dds":0.6475195822454308,"last_synced_commit":"ac53fb9deaf69e4a1d902762e6279a7b4550d691"},"previous_names":["featureserver/featureserver"],"tags_count":109,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/koopjs%2FFeatureServer","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/koopjs%2FFeatureServer/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/koopjs%2FFeatureServer/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/koopjs%2FFeatureServer/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/koopjs","download_url":"https://codeload.github.com/koopjs/FeatureServer/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":221531409,"owners_count":16838775,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["arcgis","deprecated","geojson","geoservices","gis","javascript","server"],"created_at":"2024-07-31T00:01:38.487Z","updated_at":"2024-10-27T02:30:41.076Z","avatar_url":"https://github.com/koopjs.png","language":"JavaScript","funding_links":[],"categories":["JavaScript","Specifications"],"sub_categories":[],"readme":"# FeatureServer\n\n## DEPRECATED - now migrated to a package in the [Koop monorepo](https://github.com/koopjs/koop/).  Update any `npm` usage to `@koopjs/featureserver`.\n\n[![No Maintenance Intended](http://unmaintained.tech/badge.svg)](http://unmaintained.tech/)\n[![npm][npm-image]][npm-url]\n[![travis][travis-image]][travis-url]\n[![Greenkeeper badge][greenkeeper-image]][greenkeeper-url]\n\n*An open source implementation of the GeoServices specification*\n\n## Usage\n\nThis is meant to be used as a plugin to Express\n\n### Example server\n```js\nconst express = require('express')\nconst app = express() // set up a basic express server\nconst FeatureServer = require('featureserver')\nconst cache = require('cache')\n\n// We only need one handler because FeatureServer.route is going to do all the work\nconst handler = (req, res) =\u003e {\n  cache.get(/* some geojson */, (err, data) =\u003e {\n    if (err) res.status(500).json({error: err.message})\n    else FeatureServer.route(req, res, data)\n  })\n}\n\n// Sets up all of the handled routes to support `GET` and `POST`\nconst routes = ['/FeatureServer', '/FeatureServer/layers', '/FeatureServer/:layer', '/FeatureServer/:layer/:method']\n\nroutes.forEach(route =\u003e {\n  app.route(route)\n  .get(handler)\n  .post(handler)\n})\n```\n\n### Setting defaults at runtime\nFeatureServer allows several defaults to be set at runtime via Express's `app.locals` method.  Specifically, you will need to set:\n\n```js\napp.locals.config = {\n  featureServer: {\n    // define default here\n  }\n}\n```\n\nIf you are using FeatureServer as part of a Koop instance, the equivalent of Express's `app.locals` is `koop.server.locals`.\n\nThe follow properties can be set at runtime with the noted method:\n\n```js\napp.locals.config = {\n  featureServer: {\n    currentVersion: 11.01, // defaults to 10.51\n    fullVersion: '11.0.1', // defaults to '10.5.1'\n    serviceDescription: 'default service description',\n    description: 'default layer description'\n  }\n}\n```\n\n## API\n* [FeatureServer.route](#FeatureServer.route)\n* [FeatureServer.query](#FeatureServer.query)\n* [FeatureServer.serverInfo](#FeatureServer.serverInfo)\n* [FeatureServer.layerInfo](#FeatureServer.layerInfo)\n* [FeatureServer.layers](#FeatureServer.layers)\n* [FeatureServer.generateRenderer](#FeatureServer.generateRenderer)\n* [FeatureServer.authenticate](#FeatureServer.authenticate)\n* [FeatureServer.error.authorize](#FeatureServer.error.authorize)\n* [FeatureServer.authenticate](#FeatureServer.error.authenticate)\n* [FeatureServer.queryRelatedRecords](#FeatureServer.queryRelatedRecords)\n\n### FeatureServer.route\nPass in an `incoming request object`, an `outgoing response object`, a `geojson` object, and `options` and this function will route and return a geoservices compliant response\n\n- Supports: '/FeatureServer', '/FeatureServer/layers', '/FeatureServer/:layer', '/FeatureServer/:layer/:method'\n\t- _Note_: only `query`, `info`, and `generateRenderer` are supported methods at this time.\n\n```js\nFeatureServer.route(req, res, data, options)\n```\n\n- **data** is either a geojson object extended with some additional properties or an object with a layers property which an array of extended geojson objects. These properties are optional and can be used to provide more specific metadata or to shortcut the built in filtering mechanism.\n\ne.g.\n\n```js\n{\n  type: 'FeatureCollection' // Static\n  features: Array, // GeoJSON features\n  statistics: Object, // pass statistics to an outStatistics request to or else they will be calculated from geojson features passed in\n  metadata: {\n    id: number, // The unique layer id.  If supplied for one layer, you should supply for all layers to avoid multiple layers having the same id.\n    name: String, // The name of the layer\n    description: String, // The description of the layer\n    copyrightText: String, // The copyright text (layer attribution text)\n    extent: Array, // valid extent array e.g. [[180,90],[-180,-90]]\n    displayField: String, // The display field to be used by a client\n    geometryType: String // REQUIRED if no features are returned with this object Point || MultiPoint || LineString || MultiLineString || Polygon || MultiPolygon\n    idField: String, // unique identifier field,\n    maxRecordCount: Number, // the maximum number of features a provider can return at once\n    limitExceeded: Boolean, // whether or not the server has limited the features returned\n    timeInfo: Object, // describes the time extent and capabilities of the layer,\n    transform: Object, // describes a quantization transformation\n    renderer: Object, // provider can over-ride default symbology of FeatureServer output with a renderer object. See https://developers.arcgis.com/web-map-specification/objects/simpleRenderer, for object specification.\n    defaultVisibility: boolean, // The default visibility of this layer\n    minScale: number, // The minScale value for this layer\n    maxScale: number, // The maxScale value for this layer\n    fields: [\n     { // Subkeys are optional\n       name: String,\n       type: String, // 'Date' || 'Double' || 'Integer' || 'String'\n       alias: String, // how should clients display this field name,\n     }\n    ]\n  },\n  capabilities: {\n    quantization: Boolean // True if the provider supports quantization\n  },\n  filtersApplied: {\n    all: Boolean // true if all post processing should be skipped\n    geometry: Boolean, // true if a geometric filter has already been applied to the data\n    where: Boolean, // true if a sql-like where filter has already been applied to the data\n    offset: Boolean // true if the result offset has already been applied to the data,\n    limit: Boolean // true if the result count has already been limited,\n    projection: Boolean // true if the result data has already been projected\n  }\n  count: Number // pass count if the number of features in a query has been pre-calculated\n}\n```\n\nor\n\n```js\n{\n  layers: [\n    {\n      type: 'FeatureCollection'\n      ...\n    },\n    {\n      type: 'FeatureCollection'\n      ...\n    }\n]\n```\n- **options** is an object that dictates method actions. See `FeatureServer.query` and `FeatureServer.generateRenderer` for more details.\n\n### FeatureServer.query\nPass in `geojson` and `options` (a valid [geoservices query object](https://geoservices.github.io/query.html)), and the function will perform the query and return a valid geoservices query object. The in addition to input `statistics: {}`, following is an example of _all_ query `options` that can be passed into the query route: '/FeatureServer/:layer/query'\n\ne.g.\n\n```js\nconst options = {\n  where: `1=1`,\n  objectIds: '1,2,3',\n  geometry: {\n    xmin: -110, ymin: 30, xmax: -106, ymax: 50,\n    spatialReference: { wkid: 4326 },\n  },\n  geometryType: 'esriGeometryEnvelope',\n  spatialRel: 'esriSpatialRelContains',\n  outFields: '*',\n  returnGeometry: true,\n  outSR: 102100, // output spatial reference\n  returnIdsOnly: true,\n  returnCountOnly: true,\n  orderByFields: 'Full/Part_COUNT DESC',\n  groupByFieldsForStatistics: 'Full/Part',\n  outStatistics: {\n  \tstatisticType: 'count',\n  \tonStatisticField: '\u003cfield\u003e',\n  \toutStatisticFieldName: 'name'\n  },\n  returnDistinctValues: true,\n  resultOffset: 0,\n  resultRecordCount: 0,\n  f: 'pjson'\n}\n\nFeatureServer.query(geojson, options)\n```\n\n### FeatureServer.serverInfo\nGenerate version `10.51` Geoservices server info\n\n```js\nconst server = {\n  description: String // Describes the collection of layers below,\n  copyrightText: String // Optional copyright text\n  maxRecordCount: Number // the maximum number of features a provider can return at once,\n  hasStaticData: Boolean // whether or not the server contains any data that is not changing\n  hasAttachments: Boolean // whether or not the server contains any attachments for this layer\n  layers: [{ // A collection of all the layers managed by the server\n    type: 'FeatureCollection',\n    metadata: {\n      id: number, // The unique layer id.  If supplied for one layer, you should supply for all layers to avoid multiple layers having the same id.\n      name: String, // The name of the layer\n      description: String, // The description of the layer\n      extent: Array, // valid extent array e.g. [[180,90],[-180,-90]]\n      displayField: String, // The display field to be used by a client\n      idField: String, // unique identifier field,\n      geometryType: String, // REQUIRED if no features are returned with this object Point || MultiPoint || LineString || MultiLineString || Polygon || MultiPolygon\n      maxRecordCount: Number, // the maximum number of features a provider can return at once\n      limitExceeded: Boolean, // whether or not the server has limited the features returned\n      timeInfo: Object, // describes the time extent and capabilities of the layer\n      renderer: Object, // provider can over-ride default symbology of FeatureServer output with a renderer object. See https://developers.arcgis.com/web-map-specification/objects/simpleRenderer, for object specification.\n      defaultVisibility: boolean, // The default visibility of this layer\n      minScale: number, // The minScale value for this layer\n      maxScale: number // The maxScale value for this layer\n    }\n    features: [// If all the metadata provided above is provided features are optional.\n      {\n        type: 'Feature',\n        geometry: {\n          type: 'Point',\n          coordinates: [125.6, 10.1]\n        },\n        properties: {\n          name: 'Dinagat Islands'\n        }\n      }]\n    }\n  }],\n  tables: [{ // A collection of all the tables managed by the server\n    type: 'FeatureCollection',\n    metadata: {\n      // see layer metadata\n    }\n  }],\n  relationships: [{ // A collection of all relationships manged by the server\n    id: number, // The unique relationship id.\n    name: String, // The name of the relationship\n  }]\n}\n\nFeatureServer.serverInfo(server)\n```\n\n### FeatureServer.layerInfo\nGenerate version `10.51` Geoservices information about a single layer\n```js\nFeatureServer.layerInfo(geojson, options)\n```\n\nNote that the layer info is modified with properties `metadata` and `capabilites` found at the top-level of the GeoJSON object.\n\n|GeoJSON property| Layer info result|\n|---|---|\n|`metadata.id`| overrides default|\n|`metadata.name`| overrides default|\n|`metadata.description`| overrides default |\n|`metadata.geometryType`| overrides value determined from data |\n|`metadata.extent`| overrides value determined from data |\n|`metadata.timeInfo`| overrides default |\n|`metadata.maxRecordCount`| overrides default (2000) |\n|`metadata.displayField`| overrides default (`OBJECTID`) |\n|`metadata.objectIdField`| overrides default  (`OBJECTID`) |\n|`metadata.hasStaticData`| overrides default (`false`) |\n|`metadata.hasAttachments`| overrides default (`false`) |\n|`metadata.renderer`| overrides default |\n|`metadata.defaultVisibility`| overrides default |\n|`metadata.minScale`| overrides default |\n|`metadata.maxScale`| overrides default |\n|`metadata.relationships` | overrides default |\n|`capabilities.extract`|  when set to `true`, `Extract` added to `capabilites` (e.g., `capabilities: \"Query,Extract\"`) |\n|`capabilities.quantization`| when set to `true`, `supportsCoordinatesQuantization: true` |\n\n##### metadata.relationships\nThis defined the server managed relationships for the layer\n\ne.g.\n\n```js\nconst metadata = {\n  //...\n  relationships: [{ // A collection of all relationships manged by the server\n    id: number, // The unique relationship id.\n    name: String, // The name of the relationship\n    relatedTableId: number, // Id of the layer/table related records are found\n    cardinality: String, // esriRelCardinalityOneToMany | esriRelCardinalityManyToMany\n    role: String, // esriRelRoleOrigin | esriRelRoleDestination\n    keyField: String, // key field name in the related Table \n    composite: Boolean // likely to false\n  }]\n  //...\n}\n```\n\n### FeatureServer.layers\nGenerate version `10.51` Geoservices information about one or many layers\n\nCan pass a single geojson object or an array of geojson objects\n```js\nFeatureServer.layers(geojson, options)\n```\n\n### FeatureServer.generateRenderer\nPass in `geojson` and `options`, and the function will return a valid generateRenderer object. Two `classificationDef` classification types are supported, _classBreaksDef_ and _uniqueValueDef_.\n\n_classBreaksDef_ is used to classify numeric data based on a number of breaks and a statistical method. Features can also be normalized before being classified. _uniqueValueDef_ is used to classify data based on a unique field(s). If classification breaks are not supplied through in `statistics`, they will be generated using `classificationDef` options. The output is a _generateRenderer_ object.\n\n##### classBreaksDef\nIn addition to class breaks as input `statistics: []`, the following is an example of _all_ classBreaksDef `options` that can be passed into the generateRenderer route: '/FeatureServer/:layer/generateRenderer'\n\ne.g.\n\n```js\nconst options = {\n *'classificationDef': {\n   *'type': 'classBreaksDef',\n   *'classificationField': '\u003cfield1\u003e',\n   *'classificationMethod': 'esriClassifyEqualInterval' | 'esriClassifyNaturalBreaks' | 'esriClassifyQuantile' | 'esriClassifyStandardDeviation',\n   *'breakCount': 9,\n    'normalizationType': 'esriNormalizeByField' | 'esriNormalizeByLog' | 'esriNormalizeByPercentOfTotal',\n    'normalizationField': '\u003cfield2\u003e' // mandatory if 'normalizationType' === 'esriNormalizeByField'\n    'baseSymbol': {\n      'type': 'esriSMS',\n      'style': 'esriSMSCircle',\n      'width': 2\n    },\n    'colorRamp': {\n      'type': 'algorithmic',\n      'fromColor': [115,76,0,255],\n      'toColor': [255,25,86,255],\n      'algorithm': 'esriHSVAlgorithm'\n    }\n  },\n  'where': '\u003cfield2\u003e \u003e 39'\n}\n\nFeatureServer.generateRender(geojson, options)\n\n*required\n```\n\nOutput:\n\n```js\n{\n  type: 'classBreaks',\n  field: '\u003cfield1\u003e',\n  classificationMethod: 'esriClassifyEqualInterval',\n  minValue: 0,\n  classBreakInfos: [\n    {\n      classMinValue: 0,\n      classMaxValue: 5,\n      label: '0-5',\n      description: '',\n      symbol: {\n        type: 'esriSMS',\n        style: 'esriSMSCircle',\n        width: 2,\n        color: [115, 76, 0]\n      }\n    },\n    {\n      classMinValue: 6,\n      classMaxValue: 11,\n      label: '6-11',\n      description: '',\n      symbol: {\n        type: 'esriSMS',\n        style: 'esriSMSCircle',\n        width: 2,\n        color: [156, 67, 0]\n      }\n    },\n    ...\n  ]\n}\n```\n\n##### uniqueValueDef\nThe following is an example of _all_ uniqueValueDef `options` that can be passed into the generateRenderer route: '/FeatureServer/:layer/generateRenderer'\n\ne.g.\n\n```js\nconst options = {\n *'classificationDef': {\n   *'type': 'uniqueValueDef',\n   *'uniqueValueFields': ['Genus', '\u003cfield2\u003e', '\u003cfield3\u003e'],\n   *'fieldDelimiter': ', '\n    'baseSymbol': {\n      'type': 'esriSMS',\n      'style': 'esriSMSCircle',\n      'width': 2\n    },\n    'colorRamp': {\n      'type': 'algorithmic',\n      'fromColor': [115,76,0,255],\n      'toColor': [255,25,86,255],\n      'algorithm': 'esriHSVAlgorithm'\n    }\n  },\n  'where': 'latitude \u003e 39'\n}\n\nFeatureServer.generateRender(geojson, options)\n\n*required\n```\n\nOutput:\n\n```js\n{\n  type: 'uniqueValue',\n  field1: 'Genus',\n  field2: '',\n  field3: '',\n  fieldDelimiter: ', ',\n  defaultSymbol: {},\n  defaultLabel: '',\n  uniqueValueInfos: [\n    {\n      value: 'MAGNOLIA',\n      count: 5908,\n      label: 'MAGNOLIA',\n      description: '',\n      symbol: {\n        type: 'esriSMS',\n        style: 'esriSMSCircle',\n        width: 2,\n        color: [115, 76, 0]\n  \t   }\n    },\n    {\n      value: 'QUERCUS',\n      count: 12105,\n      label: 'QUERCUS',\n      description: '',\n      symbol: {\n        type: 'esriSMS',\n        style: 'esriSMSCircle',\n        width: 2,\n        color: [116, 76, 0]\n  \t  }\n   },\n   ...\n  ]\n```\n\n### FeatureServer.authenticate\nPass in an outgoing response object and an authentication success object and this function will route and return a formatted authentication success response.\n\n    FeatureServer.authenticate(res, auth, ssl = false)\n\n* `auth` is the result of a successful authentication attempt that returns a token and expiration time\n* `ssl` is a boolean flag indicating if token should always be passed back via HTTPS. Defaults to `false`\n\ne.g.,\n    \n    const auth = {\n      \"token\":\"elS39KU4bMmZQgMXDuswgA14vavIp4mfpiqcWSr0qM6q4dFguTnnHddWqbpK5Mc3HsCN8XghlwawUUYApOOcxKNyg_9WqTofChJXxxD058_rL1HZkM5PDhUOh9YYQn1K\",\n      \"expires\":1524508236322\n    }\n\n    FeatureServer.authenticate(res, auth)\n\n    {\n      \"token\":\"elS39KU4bMmZQgMXDuswgA14vavIp4mfpiqcWSr0qM6q4dFguTnnHddWqbpK5Mc3HsCN8XghlwawUUYApOOcxKNyg_9WqTofChJXxxD058_rL1HZkM5PDhUOh9YYQn1K\",\n      \"expires\":1524508236322,\n      ssl: false\n    }\n\n### FeatureServer.error.authorize\nPass in an outgoing response object and this function will route and return a formattted authorization error.\n\n    FeatureServer.error.authorize(res)\n\n    {\n      \"error\": {\n        \"code\": 499,\n        \"message\": \"Token Required\",\n        \"details\": []\n      }\n    }\n\n### FeatureServer.error.authenticate\nPass in an outgoing response object and this function will route and return a formatted authentication error.\n\n    FeatureServer.error.authenticate(res)\n    \n    {\n      \"error\": {\n        \"code\": 400,\n        \"message\": \"Unable to generate token.\",\n        \"details\": [\"Invalid username or password.\"]\n      }\n    }\n\n\n[npm-image]: https://img.shields.io/npm/v/featureserver.svg?style=flat-square\n[npm-url]: https://www.npmjs.com/package/featureserver\n[travis-image]: https://img.shields.io/travis/koopjs/FeatureServer.svg?style=flat-square\n[travis-url]: https://travis-ci.org/koopjs/FeatureServer\n[greenkeeper-image]: https://badges.greenkeeper.io/koopjs/FeatureServer.svg\n[greenkeeper-url]: https://greenkeeper.io/\n\n## Unreleased\n\n### FeatureServer.queryRelatedRecords\nPass in `geojson` and `options`, and the function will return a valid queryRelatedRecords object. Required attributes within `options` are `objectIds` and `relationshipId`.\n\nThe `geojson` should be in the special FeatureCollection of FeatureCollections format to show the relationship between requested Features within the layer/table and the referenced relatinoship's features.\ne.g. \n\n```js\n  const geojson = {\n    \"type\": \"FeatureCollection\",\n    \"features\": [ // Array of FeatureCollections by objectId with the related records as features\n      {\n        \"type\": \"FeatureCollection\",\n        \"properties\": {\n          \"OBJECTID\": 37\n        },\n        \"features\": [\n          {\n            \"type\": \"Feature\",\n            \"geometry\": {...},\n            \"properties\": {...}\n          }\n        ]\n      } \n    ]\n  }\n  \n  const options = {\n    objectIds: \"37, 462\", // comma separated string of object ids within the layer to get related records \n    relationshipId: 4, // relationship Id of the server manged relationship of the layer, see FeatureServer.layerInfo\n  }\n\nFeatureServer.queryRelatedRecords(geojson, options)\n```\n\nOutput:\n\n```js\n{\n  \"geometryType\": \"esriGeometryPolygon\",\n  \"spatialReference\": {\n    \"wkid\": 4267\n  },\n  \"fields\": [\n    {\n      \"name\": \"OBJECTID\", \n      \"type\": \"esriFieldTypeOID\", \n      \"alias\": \"OBJECTID\"\n    }, \n    {\n      \"name\": \"FIELD1\", \n      \"type\": \"esriFieldTypeString\", \n      \"alias\": \"FIELD1\", \n      \"length\": 25\n    }\n  ],\n  \"relatedRecordGroups\": [\n    {\n      \"objectId\": 37,\n      \"relatedRecords\": [\n        {\n          \"attributes\": {\n            \"OBJECTID\": 5540,\n            \"FIELD1\": \"1000147595\"\n          },\n          \"geometry\": {...}\n        }\n      ]\n    }\n  ]\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkoopjs%2FFeatureServer","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkoopjs%2FFeatureServer","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkoopjs%2FFeatureServer/lists"}