Loading TOC...
Search Developer's Guide (PDF)

Search Developer's Guide — Chapter 14

Geospatial Search Applications

This chapter describes how to use the geospatial features of MarkLogic and describes the type of applications that might use these functions. MarkLogic supports geospatial data represented in either XML or JSON, and supports geospatial search in several languages, include XQuery, Server-Side JavaScript, Java, and Node.js.

This chapter includes the following sections:

Terms and Definitions

You should be familiar with the following terms and definition before using geospatial features of MarkLogic Server:

TermDefinition
coordinate systemA geospatial coordinate system is a set of mappings that map places on Earth to a set of numbers. The vertical axis is represented by a longitude coordinate, and the horizontal axis is represented by a latitude coordinate. Together they make up a coordinate system that is used to map places on the Earth. For more details, see Understanding Geodetic Coordinates.
distanceThe distance between two geospatial objects refers to the geographical closeness of those geospatial objects.
ETRS89ETRS89, or European Terrestrial Reference System 1989, is an earth-centered geodetic coordinate system. This is one of the coordinate systems you can use for computations, search and indexing of geospatial data. For details, see Multiple Coordinate Systems.
pointA geospatial point is a discrete location, identified by two coordinates. In a geospatial coordinate system, a point is the intersection of a given latitude coordinate and longitude coordinate. For more details, see Understanding Points.
point queryA point query matches points in documents against point or other region search criteria. When the criteria are expressed as points, a document matches if a point in the document is equal to the input criteria. When the criteria are expressed as other region types, a document matches if a point in the document is within the input region. Use a region query to match non-point regions in documents.
proximityThe proximity of search results is how close matches are to each other in a document. Proximity can apply to any type of search terms, including geospatial search terms. For example, you might want to find the term 'dog' within 10 words of a point in a given zip code.
rawA Euclidean coordinate system. This is one of the coordinate systems you can use for computations, search and indexing of geospatial data. For details, see Multiple Coordinate Systems.
regionA region is a set of coordinates that describe a point, box, circle, polygon, or linestring. For details, see Understanding Coordinate Systems.
region queryA region query matches regions in documents against region search criteria. A document matches if a region in the document satisfies a specified relationship with the input regions, such as overlaps, intersects, contains, or within. When searching for matching points, you should usually use a point query instead of a region query. For details, see Searching for Matching Regions.
toleranceA distance within which two points are considered equal, a point is considered 'on' an edge, or two edges are considered 'touching', even when the coordinate values do match exactly. For details, see Understanding Tolerance.
WGS84WGS84, or World Geodetic System version 1984, is an earth-centered geodetic coordinate system. This is one of the coordinate systems you can use for computations, search and indexing of geospatial data. For details, see Multiple Coordinate Systems.
WKTWKT, or Well Known Text, is a common string representation of geospatial data. You can convert to and from WKT and the internal MarkLogic representation of a region or point. For details, see Converting To and From Common Geospatial Representations.
WKBWKB, or Well Known Binary, is a common binary representation of geospatial data. You can convert to and from WKT and the internal MarkLogic representation of a region or point. For details, see Converting To and From Common Geospatial Representations.
governing coordinate systemThe coordinate system/precision combination in effect during a geospatial operation. For details, see The Governing Coordinate System.

Geospatial Features Overview

This section provides a brief overview of key features of the geospatial capabilities of MarkLogic Server. Each topic includes pointers to deeper discussion of the feature. The following topics are covered:

Search for Points, Polygons, and Other Regions

In MarkLogic, you can construct searches based on either points (discrete locations) or regions (areas). A geospatial query can match points, polygons, and other regions in your documents against points, boxes, circles, polygons, complex polygons, and linestrings search criteria.

You can compare points for equality to other points or for containment in regions. You can compare polygons and other regions using a rich set of topological operators that includes containment, overlap, and intersection.

For example, you can use geospatial search in MarkLogic to find documents matching criteria such as the following:

  • Match points against other points. For example, find documents containing this point.
  • Match points within regions. For example, find documents containing a point within this circle.
  • Match regions against each other: For example, find documents containing a polygon that overlaps this polygon, or find documents containing a region that intersects this linestring.

Notice that the first two query types match points in documents. These are called point queries. You can only use a point query to test for equality to another point or containment within a region.

To search for regions satisfying relationsips such as intersection, containment, and overlap, use a region query.

For more details, see:

Geospatial Type System

The geospatial interfaces in MarkLogic operate on a geospatial type hierarchy based on point and region primitive types. The type system includes region 'subtypes' for specific region types, such as circle, box, and linestring.

The cts:point XQuery type and cts.point JavaScript type represents a point. Points are used as building blocks for the region types. The cts:region XQuery type and the cts.region JavaScript is the base type for all regions.

The geospatial interfaces include constructors for creating points and all supported region types. For example, you can create a polygon value using the cts:polygon XQuery constructor or the cts.polygon JavaScript constructor.

For more details, see Constructing Geospatial Point and Region Values.

Multiple Coordinate Systems

The geospatial data can be expressed in one of several coordinate systems, including WGS84, ETRS89, and raw. WGS84 and ETRS89 are earth-centered geodetic coordinate systems. Raw is a flat plane, cartesian coordinate system. For more details, see Supported Coordinate Systems.

MarkLogic also supports both single and double precision coordinates for each coordinate system. The precision is coupled with the coordinate system in most contexts. For example, when constructing a geospatial point index, your choice of coordinate system includes both 'wgs84' (single precision) and 'wgs84/double' (double precision).

For details, see the following topics:

Support for Common Geospatial Representations

Many MarkLogic interfaces work with geospatial data in common formats, such as Well Known Text (WKT), Well Know Binary (WKB), KML, GML, and GeoJson.

For more details, see:

Flexible Data Layout

Geospatial data in MarkLogic is stored in XML elements and/or attributes, and JSON properties. The coordinates of a point or region thus stored can be represented in several different ways. You can also identify the location of your geospatial data in several different ways.

For point queries, you can specify the location of coordinates in your documents by XPath expression, XML element name, XML element attribute name, or JSON property name. In addition, the coordinates of a point can be either a single, compound value ("10.5 32.7") or separate latitude and longitude values.

For region queries, specify the location of the region coordinates using an XPath expression. Region coordinates must be stored as WKT or serialized cts:region values.

Coordinates can also be stored, indexed, and interpreted as either single or double precision values. The original precision is always preserved in your documents, but the configured precision determines the precision at which coordinates are indexed and interpreted during computations.

For more details, see:

Support for Single and Double Precision Coordinates

You can evaluate geospatial queries and create geospatial indexes that interpret coordinates as either single (float) or double precision values. You should usually choose single precision, unless your application requires fine-grained accuracy (less than 1 meter).

The default precision depends on your evaluation context. If you do nothing to explictly configure the precision of your App Server or evaluation context, then single precision is used.

For details, see the following topics:

Geospatial Computational Utility Functions

MarkLogic provides a rich set of geospatial utility functions, including the following:

  • Computing distance and bearing computations
  • Counting region vertices
  • Finding the point at which two arcs intersect
  • Generating a set of bounding boxes that cover a region

For more details, see Summary of Other Geospatial Operations.

Geospatial Format Conversion Functions

MarkLogic provides XQuery and JavaScript library modules to translate Metacarta, GML, KML, GeoRSS, and GeoJSON formats to MarkLogic primitive geospatial types.

The functions in these libraries are designed to convert geospatial data in supported formats and convert it into primitive MarkLogic geospatial primitive types for use with geospatial query constructors and other geospatial operations.

For more details, see the following topics:

Support in Multiple APIs

This chapter focuses on performing geospatial queries in MarkLogic Server using the cts:search XQuery function or cts.search Server-Side JavaScript function. You can also configure and use geospatial search with the following MarkLogic APIs:

Understanding Coordinate Systems

In its most basic form, geospatial data is a set of coordinates. The interpretation of the coordinates is based on a coordinate system. For example, a geodetic coordinate system inteprets the coordinates as latitude and longitude values applying to the surface of the earth.

MarkLogic supports both geodetic and Euclidean coordinate systems.

This section covers the following topics:

Understanding Points

A point represents a discrete location. In a geodetic coordinate system such as WGS84, a point represents a discrete location on the earth. In a Euclidean coordinate system such as raw, a point represents a discrete location in the Euclidean space.

A point is represented by an ordered pair of numbers called coordinates. In a geodetic coordinate system, these numbers represent latitude and longitude values on the earth; for more details, see Understanding Geodetic Coordinates. In a 2-dimensional Euclidean coordinate system, these numbers represent horizontal (x) and verical (y) values; for more details, see Understanding Euclidean Coordinates.

The cts:point XQuery type and cts.point JavaScript type represent a point in MarkLogic Server. Use the cts:point or cts.point constructor to construct a point from a pair of coordinates.

Points are also to used define the other regions in MarkLogic Server, and constructor functions are available for these regions, such cts:box in XQuery or cts.polygon in JavaScript. To learn about supported region types, see Understanding MarkLogic Geospatial Region Types.

Understanding Geodetic Coordinates

A geodetic coordinate system maps points to locations on the Earth. MarkLogic supports geodetic coordinate systems such as WGS84 and ETRS89.

The coordinates of a point in a geodetic coordinate system represent latitude and longitude positions on the Earth. A point has one latitude coordinate and one longitude coordinate. The latitude coordinate represents the north/south position of the point on the Earth. The longitude coordinate represents the east/west position of the point on the Earth.

Point coordinates are expressed in degrees, minutes of arc, and seconds of arc. Distance is measured in units such as miles, feet, kilometers, and meters.

Geospatial queries and other operations that use a geodetic coordinate system must account for the curvature of the Earth. For example, the edges of a polygon in a geodetic coordinate system are geodesic arcs (or simply 'geodesics').

Latitude values are in the range -90 to 90 degrees. The equator has latitude zero. Negative latitude values are south of the equator, with -90 at the south pole. Positive latitude values are north of the equator, with 90 at the north pole.

Longitude values are in the range -180 to 180 degrees. The Prime Meridian has longitude 0. Negative longitude values span the 180 degrees west of the Prime Meridian. Positive longitude values span the 180 degrees east of the Prime Meridian.

Understanding Euclidean Coordinates

A Euclidean coordinate system maps points to locations on a two-dimensional Euclidean plane. The 'raw' coordinate system in MarkLogic is a Euclidean coordinate system; for more details, see Raw Coordinate System.

A Euclidean coordinate can be used to represent non-Earth spatial data in local coordinate systems, such as for mathematical modeling or when projecting geographic points on to a flat plane.

A point in the raw coordinate system is represented by an (x,y) value pair, where x represents the horizontal position on the plane and y represents the vertical position. The interpretation of the coordinates is application specific, as is the range of values.

Point coordinates and distances in a Euclidean coordinate system are interpreted in an application-specific way. The units for x, y, and distance are assumed to be the same. The edges of a polygon are straight lines in a Euclidean coordinate system.

Most of the geospatial interfaces and documentation in MarkLogic refer to the coordinates of a point or region using 'latitude' and 'longitude' terminology. However, when working with a raw coordinate system, the coordinates do not actually represent latitude and longitude values. Instead, latitude refers to the x coordinate and longitude refers to the y coordinate.

Supported Coordinate Systems

MarkLogic Server supports the following coordinate systems for geospatial data:

WGS84 Coordinate System

By default, MarkLogic Server uses the World Geodetic System version 1984 (WGS84) as the basis for geocoding. WGS84 is a widely accepted standard for global point representation. WGS84 is an earth-centered geodetic coordinate system with a coordinate system origin at the Earth's center of mass.

WGS84 assumes a single map projection of the earth. WGS84 is widely used for mapping locations on the Earth, and is used by a wide range of services, including satellite services such as Global Positioning System (GPS) and Google Maps. There are other geocoding systems, some of which have advantages or disadvantages over WGS84. For example, some are more accurate in a given region, while others may be used historically in legacy data.

For details on WGS84, see http://en.wikipedia.org/wiki/World_Geodetic_System.

ETRS89 Coordinate System

The European Terrestrial Reference System (ETRS89) is an earth-centered, earth-fixed geodetic coordinate system, designed primarily for mapping locations in Europe.

This coordinate system is fixed to the stable part of the Eurasian tectonic plate. As such, it is not subject to continental drift. ETRS89 and WGS84 coordinates are not interchangeable because of this difference in the handling of continental drift.

For more details, see http://en.wikipedia.org/wiki/European_Terrestrial_Reference_System_1989.

Raw Coordinate System

The 'raw' coordinate is a Euclidean coordinate system.

The coordinates of a point in the raw coordinate system represent a position on a two-dimensional Euclidean plane. For details, see Understanding Euclidean Coordinates.

The raw coordinate system is a simple cartesian coordinate system, best suited for working with non-geospatial data. However, you can use the raw coordinate system to represent geographical points projected on to a flat plane.

The Governing Coordinate System

The governing coordinate system is the coordinate system/precision combination in effect during a geospatial operation. It affects the handling of input values, calculations, comparisons, and return values.

A precision is always implied by the coordinate system name. For example, 'wgs84' implies single precision, while 'wgs84/double' implies double precision. However, some operations accept a precision option that enables you to override the precision implicit in the coordinate system name. For details, see Specifying a Per-Operation Coordinate System and Precision.

The governing coordinate system is based on a precedence ordering of the coordinate system and precision specified in the App Server configuration, a main module prolog (XQuery only), and the parameters or options of a geospatial function (from lowest to highest precedence). For details, see How MarkLogic Selects the Governing Coordinate System.

How Precision Affects Geospatial Operations

The governing coordinate system always has an associated precision, either float (single) or double. Some operations allow you to override the precision implied by the coordinate system through an option.

  • The original precision of your data is always preserved in your documents.
  • Geospatial data is indexed using the precision configured for the index.
  • Geospatial points and regions are serialized at the precision of the governing coordinate system.
  • Comparison operations on geospatial regions use the precision of the governing coordinate system.
  • Functions operating on geospatial data interpret their input, perform their calculations, and return their results using the governing coordinate system.
  • Functions that return geospatial points or regions return either single or double precision coordinates, depending on the governing coordinate system.
  • Accessor functions for geospatial points or region return either a single or double precision value, depending on the governing coordinate system. This applies to XQuery functions such as cts:point-latitude, cts:circle-radius, cts:box-west, and their Server-Side JavaScript equivalents.
  • Latitude and longitude bounds on box functions are not truncated to the single precision range if the governing coordinate system is double precision. This applies to XQuery functions such as cts:geospatial-boxes and cts:element-geospatial-boxes, and their Server-Side JavaScript equivalents.
  • Geospatial operations perform calculations using the precision of the governing coordinate system. This applies to functions such as cts:distance, cts:polygon-contains, and cts:bounding-boxes, and their Server-Side JavaScript equivalents.
  • The input pattern parameter to value-match functions can be either single or double precision, depending on the governing coordinate system. This applies to XQuery functions such as cts:element-geospatial-value-match and their Server-Side JavaScript equivalents.
  • Searches involving geospatial queries use the precision of the governing coordinate system for determining matches and calculating scores.

Understanding MarkLogic Geospatial Region Types

This section provides a conceptual overview of the types of regions supported by MarkLogic. Points are the building block of most regions; to learn more about points, see Understanding Points.

Most geospatial interfaces in MarkLogic work with geospatial data represented as a cts:region (XQuery) or cts.region (Server-Side JavaScript), or an equivalent serialization. The cts region type is an abstraction that can represent any of the following concrete geospatial types:

Boxes

A geospatial box is a rectangular region consisting of all the points whose latitude and longitude coordinates are within the region bounds.

In a geodetic coordinate system, a box is a projection from the three-dimensional Earth onto a flat surface. On the surface of the Earth, the edges of a box are arcs. When you project the edges onto a flat plane, they become two-dimensional latitude and longitude lines, and the space defined by those lines forms a rectangle.

The following diagram illustrates the difference between the region defined by a box on the surface of the Earth and its projection into a rectangular region on a flat plane.

The north and south edges of a geospatial box are latitude lines, not geodesic arcs. The east and west edges of a box are longitude lines, which are geodesic arcs.

A point is contained in a box if its latitude coordinate is between the north and south latitude coordinates of the box, and its longitude coordinate is between the west and east longitude coordinates of the box.

In a Euclidean coordinate system, a box is simply a rectangle with boundaries defined by north, south, east, and west coordinates. No projection is necessary.

The following assumptions and restrictions apply to geospatial boxes:

  • Assuming a projection from the Earth onto a two-dimensional plane, boxes are determined by going from the south western limit to south eastern limit (even if it crosses the anti-meridian), then north to the north eastern limit (border on the poles), then west to the north western limit, then back south to the south western limit where you started.
  • The west/east extent of a box is determined by starting at the western longitude coordinate and heading east toward the eastern longitude coordinate. If the west coordinate is less than the east coordinate, the box will not cross the anti-meridian. If the east coordinate is less than the west coordinate, the box crosses the anti-meridian.
  • Similarly, the south/north extent of a box is determined by starting at the southern latitude coordinate and heading north to the northern latitude coordinate. However, you cannot cross the pole: The northern coordinate must be greater than the southern coordinate.
  • If the western and eastern coordinates are the same, the box is a meridian line segment between the southern and northern coordinates passing through that longitude coordinate.
  • If the southern and northern coordinates are the same, the box is a latitude line segment between the western and eastern coordinates passing through that longitude coordinate.
  • If the western and eastern coordinates are the same, and the southern and northern coordinates are the same, then the box is a point specified by those coordinates.
  • During a search, the query options determine whether the boundaries of a box are included in or excluded from the box. Various boundary options on the geospatial query constructors control this behavior).

The cts:box XQuery type and cts.box JavaScript type represent a box in MarkLogic Server. You can create a box using the cts:box XQuery constructor or the cts.box JavaScript constructor. You can also create a box using one of the conversion utility functions such as geogml:box (XQuery) or geojson.box (JavaScript). For more details, see Constructing Geospatial Point and Region Values.

Polygons

A geospatial polygon is a region with three or more sides. The following diagram illustrates several polygons.

In a geodetic coordinate system, a polygon can represent any area on the Earth (with the exceptions described below). For example, you might create a polygon to represent a country or a geographical region.

Polygons offer a large degree of flexibility compared to circles or boxes. In exchange for the flexibility, operations on geospatial polygons are not quite as fast or accurate as geospatial box and circle operations.

The efficiency of polygon operations is proportional to the number of sides to the polygon. For example, a typical 10-sided polygon will likely perform faster than a typical 1000-sided polygon. The speed is dependent on many factors, including where the polygon is, the nature of your geospatial data, and so on.

The following are the assumptions and restrictions associated with geospatial polygons in MarkLogic:

  • A geodetic coordinate system treats the earth as an ellipsoid, divided by geodesic arcs running through the center of the earth. In such a system, the edges of a polygon are geodesic arcs, not latitude lines.
  • Except for the equator, the shortest distance between two points at the same latitude does not follow the latitude line. You can approximate a latitude line by adding vertices evenly spaced along the latitude line. If the region to be described is a box, use a cts:box or cts.box instead of a polygon.
  • A polygon cannot include both poles. Therefore, it cannot have both poles as a boundary (regardless of whether the boundaries are included). Thus, a polygon cannot encompass the full 180 degrees of latitude.
  • The span of the arc described by a polygon edge in a geodetic coordinate system must be between 0 and 180 degrees and cannot cross a pole. If you need to span more than 180 degrees, define multiple edges that cover the desired span.
  • No two edges of a polygon or complex polygon may overlap or cross.
  • A geospatial query is constrained to the XML elements, XML attributes, and JSON properties identified in the query constructor. To cross multiple formats in a single search, use cts:or-query in XQuery or cts.orQuery in JavaScript to combine multiple geospatial queries.
  • Coordinate system is considered at search time rather than when you construct a polygon value. Therefore, a search will throw a runtime exception if a polygon is not valid for the governing coordinate system.
  • The boundaries of a polygon are either in or out of the polygon, depending on the operation and query options. The DE9IM operators include specific boundary behaviors; for other operations, you can use query constructor options to control the boundary behavior.
  • Because of the ellipsoid Earth assumption, results are not perfectly accurate. You can use precision to affect the degree of accuracy, but results will still not be perfectly accurate.

You can construct a polygon by specifying the points that make up the vertices of the polygon. All points that are bounded by the resulting region are defined to be contained within the region.

For details, see the cts:polygon XQuery function or the cts.polygon JavaScript function.

Complex Polygons

A complex polygon is a polygon with one more holes. For example, the following graphic illustrates the difference between polygon and a complex polygon. The complex polygon is the shaded region in the region on the right. The unshaded region, or inner polygon, represents a hole in the outer polygon.

You can construct a complex polygon by constructing an outer polygon with zero or more outer polygons. All inner polygons must be completely contained in the interior of the outer polygon. Use the cts:complex-polygon XQuery function or the cts.complexPolygon JavaScript function to construct a complex polygon.

You can also cast a cts:complex-polygon or cts.complexPolygon with no holes (that is, with no inner polygons) to a cts:polygon or cts.polygon. If you specify multiple inner polygons, none of them should overlap each other.

Linestrings

A linestring is a connected sequence of geodesic arcs. A linestring does not necessarily form a closed loop as the boundary of a polygon does, although it is permissible for a linestring to form a closed loop. The following diagram demonstrates some examples of linestrings.

You can compare linestrings for equality or inequality. Two linestrings are equal if all of their vertices are equal, or if they are both empty.

You can cast a linestring to a polygon. The result is a 'flat' polygon that traces the same set of linestrings back to close the polygon.

To construct a linestring, use the cts:linestring XQuery function or the cts.linestring JavaScript function.

Circles

A geospatial circle consists of all the points within a certain distance (the radius) of a given center point. A geospatial region that represents a circle is defined by its center point and radius. The points that are the distance of the radius from the center define the boundary of the region.

Use the cts:circle XQuery function or the cts.circle JavaScript function to construct a circle.

Understanding Geospatial Query and Index Types

This topic discusses the types of geospatial query you can create, the index types that support each query type, and the data layout expected by each query and index type. The following topics are covered:

Introduction to Geospatial Query and Index Types

MarkLogic supports several types of query for searching geospatial data contained in documents. In general, geospatial queries fall into the following two categories, based on the kind of geospatial document content to be matched:

  • Point query: Match points in documents against points or other regions specified as input criteria. For example, 'Find all documents containing a point within this circle.'
  • Region query: Match other regions in documents that satisfy one of a number of relationships when compared to regions specified as input criteria. For example, 'Find all documents containing polygons that intersect with this polygon.'

For best performance, a point query should be supported by a corresponding geospatial index. A region query always requires a backing geospatial region index. MarkLogic supports several types of geospatial index, corresponding to the different geospatial query types.

Select a geospatial point query or index type based on the layout of your data. The query or index type varies depending on whether the data is represented in XML or JSON, and whether the point coordinates are represented as a single compound value ('lat lon') or as distinct latitude and longitude values. For example, you might use a cts:element-geospatial-query and a geospatial element index for points represented as a single compound XML element value.

The data layout for a region query or region index must be WKT or a serialized cts region, such as a cts:polygon. The region data is located within a document using an XPath expression when creating a query or index. Therefore, you use a cts:geospatial-region-query and a geospatial region path index for querying by region.

The following table summarizes the query and index types MarkLogic supports, based on the axes of geospatial content type (point or other region) and layout.

Geo Content to SearchIdentified ByExample Data LayoutMore Information
PointXPath Expression

Any coordinate pair addressable with an indexable XPath expression.

/Placemark/Point/coordinates

Geospatial Path Point Queries and Indexes
XML Layout
<coords>1.0 2.0</coords>
Geospatial XML Element Point Queries and Indexes
<container>
  <coords>1.0 2.0</coords>
<container>
Geospatial XML Element Child Point Queries and Indexes
<coords>
  <lat>1.0</lat>
  <lon>2.0</lon>
</coords>
Geospatial XML Element Pair Point Queries and Indexes
<coords lat="1.0" lon="2.0" />
Geospatial XML Attribute Pair Point Queries and Indexes
JSON Layout
{"coords": "1.0 2.0" }

{"coords": [1.0, 2.0] }
Geospatial JSON Property Point Queries and Indexes
{"container": {
  {"coords": "1.0 2.0" }
}}

{"container": {
  {"coords": [1.0, 2.0] }
}}
Geospatial JSON Property Child Point Queries and Indexes
{"coords": {
  "lat": 1.0,
  "lon": 2.0
}}
Geospatial JSON Property Pair Point Queries and Indexes
Other RegionXPath Expression

Any serialized cts region or WKT value addressable with an indexable XPath expression.

/envelope/cts-region

Geospatial Region Queries and Indexes

Geospatial Query Creation

You can create a geospatial cts query in the following ways.

You can also create a geospatial structured query or Query By Example for use with the Search API or the Client APIs. The Java and Node.js Client APIs include builder interfaces for creating structured queries.

For more details, see the sections on each query/index type elsewhere in this section and the following topics:

Geospatial Index Creation

Region queries require a region index, but an index is optional for some point queries. For best performance, you should usually create a geospatial index for both query types.

You must have a valid geospatial license key to create or use any geospatial indexes.

Use a geospatial region path index when matching regions in your documents. Use a geospatial point index when matching points in your documents; the type of point index depends on the layout of your content . For details, see Introduction to Geospatial Query and Index Types.

When creating a point index, you can specify the coordinate system, coordinate value precision, and point type (long-lat or lat-long). When creating a region index, you can specify the coordinate system and geohash precision. The default coordinate system is WGS84. The default coordinate precision is float (single precision), and the default point type is 'point' (lat-long).

When you create an index using the Admin API, index properties such as coordinate system and precision are specified through the index reference constructor function, such as admin:database-geospatial-element-index or admin:database-geospatial-region-path-index. For an example of index creation using the Admin API, see Configuring the Indexes.

You can create a geospatial index using the following methods:

For more details, see the sections on each query/index type, below.

Geospatial XML Element Point Queries and Indexes

Use a geospatial element query when the point coordinates in your documents are represented as the value of a single XML element, with the latitude and longitude values separated by whitespace or punctuation (except +, -, or .). For example:

<coords>37.52  -122.25</coords>

By default, the first coordinate is the latitude value, and the second coordinate is the longitude value. You can override the default order by specifying a longitude-first ordering when creating queries and indexes.

If the element value contains other coordinates, they are ignored. For example, KML data can include an additional altitude coordinate. The altitude can be present but is ignored.

When you use a geospatial element query, you should also create a corresponding geospatial element index for best performance.

For JSON documents with similar layout, see Geospatial JSON Property Point Queries and Indexes.

You can use the following interfaces to create a geospatial element query:

InterfaceQuery Constructor
XQuerycts:element-geospatial-query
Server-Side JavaScriptcts.elementGeospatialQuery
Structured Querygeo-elem-query
Java Client API
com.marklogic.client.query.StructuredQueryBuilder.geoElement
  plus
com.marklogic.client.query.StructuredQueryBuilder.geospatial 
Node.js Client APIqueryBuilder.geoElement

You can use the following interfaces to create a geospatial element index:

InterfaceIndex Construction Method
Admin InterfaceDatabases >...> Geospatial Point Indexes > Geospatial Element Indexes
XQuery Admin API (also usable with JavaScript)admin:database-add-geospatial-element-index
REST Management APIPUT /manage/v2/databases/{id|name}/properties

Geospatial XML Element Child Point Queries and Indexes

Use a geospatial element child index for geospatial point data when the coordinates are contained in an XML element value, separated by whitespace or punctuation (except +, -, or .), and you want to identify the container element as a child of another specific element. For example:

<parent-name>
  <child-name>37.52  -122.25</child-name>
</parent-name>

By default, the first coordinate is the latitude value, and the second coordinate is the longitude value. You can override the default order by specifying a longitude-first ordering when creating queries and indexes.

If the element value contains other coordinates, they are ignored. For example, KML data can include an additional altitude coordinate. The altitude can be present but is ignored.

When you use a geospatial element child query, you should also create a corresponding geospatial element child index for best performance.

For JSON documents with similar layout, see Geospatial JSON Property Child Point Queries and Indexes.

You can use the following interfaces to create a geospatial element child query:

InterfaceQuery Constructor
XQuerycts:element-child-geospatial-query
Server-Side JavaScriptcts.elementChildGeospatialQuery
Structured Querygeo-elem-query
Java Client API
com.marklogic.client.query.StructuredQueryBuilder.geoElement
  and
com.marklogic.client.query.StructuredQueryBuilder.geospatial
Node.js Client APIqueryBuilder.geoElement

You can use the following interfaces to create a geospatial element child index:

InterfaceIndex Construction Method
Admin InterfaceDatabases > ... > Geospatial Point Indexes > Geospatial Element Child Indexes
XQuery Admin API (also usable with JavaScript)admin:database-add-geospatial-element-child-index
REST Management APIPUT /manage/v2/databases/{id|name}/properties

Geospatial XML Element Pair Point Queries and Indexes

Use a geospatial element pair index for geospatial point data when the longitude and latitude are values in two different elements that are children of the same parent element. For example:

<container-name>
  <latitude>37.52</latitude>
  <longitude>-122.25</longitude>
</container-name>

For JSON data requirements, see Geospatial JSON Property Pair Point Queries and Indexes.

You can use the following interfaces to create a geospatial element pair query:

InterfaceQuery Constructor
XQuerycts:element-pair-geospatial-query
Server-Side JavaScriptcts.elementPairGeospatialQuery
Structured Querygeo-elem-pair-query
Java Client API
com.marklogic.client.query.StructuredQueryBuilder.geoElementPair
  and
com.marklogic.client.query.StructuredQueryBuilder.geospatial 
Node.js Client APIqueryBuilder.geoElementPair

You can use the following interfaces to create a geospatial element pair index:

InterfaceIndex Construction Method
Admin InterfaceDatabases > ... > Geospatial Point Indexes > Geospatial Element Pair Indexes
XQuery Admin API (also usable with JavaScript)admin:database-add-geospatial-element-child-index
REST Management APIPUT /manage/v2/databases/{id|name}/properties

Geospatial XML Attribute Pair Point Queries and Indexes

Use a geospatial attribute pair index for geospatial point data when the longitude and latitude are values in two different attributes of the same parent XML element. For example:

<element-name latitude="37.52" longitude="-122.25"/>

When you use a geospatial attribute pair query, you should also create a corresponding geospatial attribute pair index for best performance.

You can use the following interfaces to create a geospatial element attribute pair query:

InterfaceQuery Constructor
XQuerycts:element-attribute-pair-geospatial-query
Server-Side JavaScriptcts.elementAttributePairGeospatialQuery
Structured Querygeo-attr-pair-query
Java Client API
com.marklogic.client.query.StructuredQueryBuilder.geoElement
  and
com.marklogic.client.query.StructuredQueryBuilder.geospatial
Node.js Client APIqueryBuilder.geoElement

You can use the following interfaces to create a geospatial element attribute pair index:

InterfaceIndex Construction Method
Admin InterfaceDatabases > ... > Geospatial Point Indexes > Geospatial Attribute Pair Indexes
XQuery Admin API (also usable with JavaScript)admin:database-add-geospatial-element-attribute-pair-index
REST Management APIPUT /manage/v2/databases/{id|name}/properties

Geospatial Path Point Queries and Indexes

Use a geospatial path query and index for matching points when you want to express the location of the points using an XPath expression. The data layout must be one of the following:

  • A single XML element value with the latitude and longitude coordinates separated by whitespace or punctuation, as for a geospatial element query.
  • A single JSON property value with the latitude and longitude coordinates separated by whitespace or punctuation, as for a geospatial JSON property query.
  • A JSON array value containing a latitude element and a longitude element, as for a geospatial JSON property query.

By default, the first coordinate is the latitude value, and the second coordinate is the longitude value. You can override the default order by specifying a longitude-first ordering when creating queries and indexes.

The following table demonstrates the XPath expression to use when creating a path range index for several forms of example geospatial data.

Document TypeExample DataIndexing Path Expression
XML
<a:data>
  <a:geo>37.52 -122.25</a:geo>
</a:data>
/a:data/a:geo
XML
<a:data>
  <a:geo data="37.52 -122.25"/>
</a:data>
/a:data/a:geo/@data
JSON
{ "geometry" : {
    "type": "Point",
    "coordinates": [37.52,-122.25]
  }
}
/geometry[type="Point"]/array-node("coordinates")

Once you create a geospatial path range index, you cannot change the path expression. To change the path, you must remove the existing geospatial path range index and create a new one.

You can use the following interfaces to create a geospatial path query:

InterfaceQuery Constructor
XQuerycts:path-geospatial-query
Server-Side JavaScriptcts.pathGeospatialQuery
Structured Querygeo-path-query
Java Client API
com.marklogic.client.query.StructuredQueryBuilder.geoPath
  and
com.marklogic.client.query.StructuredQueryBuilder.geospatial
Node.js Client APIqueryBuilder.geoPath and queryBuilder.geospatial

You can use the following interfaces to create a geospatial path index:

InterfaceIndex Construction Method
Admin InterfaceDatabases > ... > Geospatial Point Indexes > Geospatial Path Indexes
XQuery Admin API (also usable with JavaScript)admin:database-add-geospatial-path-index
REST Management APIPUT /manage/v2/databases/{id|name}/properties

Geospatial JSON Property Point Queries and Indexes

Use a geospatial element index to index geospatial data in JSON documents when the point coordinates are contained in a single JSON property. The geospatial data must be represented in the property value as either whitespace/punctuation separated values in a string, or as an array of values. For example:

"prop-name": "37.52 -122.25"
"prop-name": [37.52, -122.25]

By default, the first coordinate is the latitude value, and the second coordinate is the longitude value. You can override the default order by specifying a longitude-first ordering when creating queries and indexes. The property value can include other entries, but they are ignored (for example, KML has an additional altitude coordinate, which can be present but is ignored).

You can use the following interfaces to create a geospatial JSON property query:

InterfaceQuery Constructor
XQuerycts:json-property-geospatial-query
Server-Side JavaScriptcts.jsonPropertyGeospatialQuery
Structured Querygeo-json-property-query
Java Client API
com.marklogic.client.query.StructuredQueryBuilder.geoJSONProperty
  and
com.marklogic.client.query.StructuredQueryBuilder.geospatial
Node.js Client APIqueryBuilder.geoProperty

You can use the following interfaces to create an index for a geospatial JSON property query. Note you should create a geospatial element index, even though you are indexing JSON content.

InterfaceIndex Construction Method
Admin InterfaceDatabases > ... > Geospatial Point Indexes > Geospatial Element Indexes
XQuery Admin API (also usable with JavaScript)admin:database-add-geospatial-element-index
REST Management APIPUT /manage/v2/databases/{id|name}/properties

Geospatial JSON Property Child Point Queries and Indexes

Use a geospatial element child index to index geospatial data in JSON when you want to limit the index to coordinate properties contained in a specific property. The geospatial data must be represented in the child property value as either whitespace/punctuation separated values in a string, or as an array of values.

For example, if your data looks like one of the following, you could create a geospatial element child index specifying "theParent" as the parent element (property) and "theChild" as the child element (property).

"theParent": {
  "theChild": "37.52 -122.25"
}
"theParent": {
  "theChild": [37.52, -122.25]
}

By default, the first coordinate is the latitude value, and the second coordinate is the longitude value. You can override the default order by specifying a longitude-first ordering when creating queries and indexes. The property value can include other entries, but they are ignored (for example, KML has an additional altitude coordinate, which can be present but is ignored).

You can use the following interfaces to create a geospatial JSON property child query:

InterfaceQuery Constructor
XQuerycts:json-property-child-geospatial-query
Server-Side JavaScriptcts.jsonPropertyChildGeospatialQuery
Structured Querygeo-json-property-query
Java Client API
com.marklogic.client.query.StructuredQueryBuilder.geoJSONProperty
  and
com.marklogic.client.query.StructuredQueryBuilder.geospatial
Node.js Client APIqueryBuilder.geoProperty

You can use the following interfaces to create an index for a geospatial JSON property child query. Note you should create a geospatial element child index, even though you are indexing JSON content.

InterfaceIndex Construction Method
Admin InterfaceDatabases > ... > Geospatial Point Indexes > Geospatial Element Child Indexes
XQuery Admin API (also usable with JavaScript)admin:database-add-geospatial-element-child-index
REST Management APIPUT /manage/v2/databases/{id|name}/properties

Geospatial JSON Property Pair Point Queries and Indexes

Use a geospatial element pair index to index geospatial data in JSON when the point coordinates are contained in sibling JSON properties. For example, use this type of index when working with data similar to the following:

"theParent" : {
  "latitude": 37.52,
  "longitude": -122.25
}

You can use the following interfaces to create a geospatial JSON property pair query:

InterfaceQuery Constructor
XQuerycts:json-property-pair-geospatial-query
Server-Side JavaScriptcts.jsonPropertyPairGeospatialQuery
Structured Querygeo-json-property-pair-query
Java Client API
com.marklogic.client.query.StructuredQueryBuilder.geoJSONPropertyPair
  and
com.marklogic.client.query.StructuredQueryBuilder.geospatial 
Node.js Client APIqueryBuilder.geoPropertyPair

You can use the following interfaces to create an index for a geospatial JSON property pair query. Note you should create a geospatial element pair index, even though you are indexing JSON content.

InterfaceIndex Construction Method
Admin InterfaceDatabases > ... > Geospatial Point Indexes > Geospatial Element Pair Indexes
XQuery Admin API (also usable with JavaScript)admin:database-add-geospatial-element-pair-index
REST Management APIPUT /manage/v2/databases/{id|name}/properties

Geospatial Region Queries and Indexes

Use a geospatial region path index to index geospatial regions, such as polygons, rather than points. A geospatial region path index supports operations such as the cts:geospatial-region-query XQuery function and the cts.geospatialRegionQuery JavaScript function. These functions enable you to test for relationships between regions, such as overlaps and contains.

Region indexes over geodetic coordinate systems are based on geohashing. Geohashes of circles are calculated by approximating the circle by a polygon. The approximation is accurate to within 0.001% of the radius of the circle. If you require more precision, use geo:circle-polygon to convert circles in your data.

When working with large circular regions, you might need to adjust the tolerance in your geospatial operations. For details, see Understanding Tolerance.

The content referenced by the path expression in a geospatial region index must be a region represented as either WKT or a serialized cts:region. For example:

FormatExample DataIndexing Path Expression
XML
<a:data>
  <a:region>POLYGON ((30 10, 40 40, 20 40, 10 20, 30 10))</a:region>
</a:data>
/a:data/a:region
XML
<a:data>
  <a:loc region="POLYGON ((30 10, 40 40, 20 40, 10 20, 30 10))"/>
</a:data>
/a:data/a:loc/@region
JSON
{ "location" : {
    "region": "POLYGON ((30 10, 40 40, 20 40, 10 20, 30 10))"
  }
}
/location/region

If your data is not in the expected format, you can use an 'envelope pattern' to encapsulate your original data along with a supported format. For more details, see Example: Using the Envelope Pattern to Encode Regions.

You can use the following interfaces to create a geospatial region path query. For more details, see Searching for Matching Regions.

InterfaceQuery Constructor
XQuerycts:geospatial-region-query
Server-Side JavaScriptcts.geospatialRegionQuery
Structured Querygeo-region-path-query and geo-region-constraint-query
Java Client API
com.marklogic.client.query.StructuredQueryBuilder.geoRegionPath
  and
com.marklogic.client.query.StructuredQueryBuilder.geospatial
Node.js Client APIqueryBuilder.geoPath and queryBuilder.geospatialRegion

You can use the following interfaces to create a geospatial region path index.

InterfaceIndex Construction Method
Admin InterfaceDatabases > ... > Geospatial Region Indexes
XQuery Admin API (also usable with JavaScript)admin:database-add-geospatial-region-path-index
REST Management APIPUT /manage/v2/databases/{id|name}/properties

Geospatial Index Positions

Each geospatial point index has a range value positions option. Enabling range value positions speeds up queries that constrain a search by the distance between geospatial data and other search terms in a document, such as when using cts:near-query in XQuery or cts.nearQuery in Javascript.

Additionally, enabling element positions improves index resolution (more accurate estimates) for XML element and JSON property queries that involve geospatial point queries (with a geospatial index with positions enabled for the geospatial data).

Geospatial Lexicons

Geospatial point indexes enable geospatial lexicon lookups. The lexicon lookups enable very fast retrieval of geospatial values. For details on geospatial lexicons, see Geospatial Lexicons.

Index Reference Resolution

Many geospatial operations either require or will take advantage of available geospatial indexes. Depending on the operation, the index reference might be explicit or implicit. For example, if you supply a cts:reference to an operation, the index reference is explicit. By contrast, when you supply an XPath expression, XML element QName, or JSON property name to a query constructor, the index reference is implicit.

Often, an index reference doesn't fully specify the characteristics of an index. For example, if you create a region path query and specify no options, you've only supplied the type of index (geospatial region path index) and the path. You have not explicitly specified the coordinate system, precision, or point type. Thus, they implicitly default to 'wgs84', single, and 'point', respectively.

MarkLogic attempts to resolve an index reference from the information in the call, including options, plus the defaults. If this is sufficient to identify a unique index, that index will be used. If it is not, an error is raised.

For example, suppose you create a geospatial region index on the path /coordinates, with coordinate-system and precision 'wgs84/double'. If you then construct a region query on the path /coordinates and specify the option 'coordinate-system=wgs84', the precision is implicitly single precision, which will not match the only available index. You will get a XDMP-GIDXNOTFOUND error.

Similarly, suppose you create one geospatial region index on the path /coordinates, with coordinate-system and precision 'wgs84/double' and another on the same path with 'wgs84' (single precision). If you then create a region path query on /coordinates and do not specify the coordinate system, the index reference is ambiguous and you will get a XDMP-GIDXAMBIGUOUS error.

Searching for Matching Points

This section describes how to use a point query to find documents containing specific points or documents containing points in specific regions. You should use a point query rather than a region query when searching for points because point queries are usually faster than region queries.

This section covers the following topics:

Point Search Overview

A point query finds documents containing one or more points that match search criteria regions. The search criteria regions can be points, circles, linestrings, polygons, or any other cts region type. (To find matching regions, rather than points, see Searching for Matching Regions).

The following are key features of searching with point queries:

  • A point matches a criteria region if it is contained in the region.
  • You can use options to control whether or not the criteria region boundaries should be considered in the match. Boundaries are included by default.
  • You can use point queries with the same search framework as other kinds of queries, such as cts:search, cts.search, jsearch.documents, search:search, or the Client APIs.
  • You can use a point query by itself or as a component of a more complex query, such as a cts:and-query (XQuery) or cts.andQuery (JavaScript).
  • You can construct a geospatial point query using an XQuery or JavaScript query constructor, by parsing query text, or using the REST, Java, or Node.js Client APIs.
  • Creating appropriate geospatial point indexes can improve speed and accuracy.

    Indexes are required for certain kinds of queries, such as range queries. Indexes are optional for queries such as value queries, but only if you use unfiltered search. For details, see Fast Pagination and Unfiltered Searches in the Query Performance and Tuning Guide.

For example, the following search uses an element child geospatial query to match documents containing at least one point in the circle with center (37.5073428,-122.2465038) and radius 1 mile. The circle criteria region is constructued using the cts:circle XQuery function or cts.circle JavaScript function.

LanguageExample
XQuery
xquery version "1.0-ml";
cts:search(fn:collection("geo-example"),
  cts:element-child-geospatial-query(
    fn:QName("http://www.opengis.net/kml/2.2", "Point"),
    fn:QName("http://www.opengis.net/kml/2.2", "coordinates"),
    cts:circle(1, cts:point(37.5073428,-122.2465038)),
    ("type=long-lat-point")
  )
)//*:name/fn:data()
(: returns just the names of the matched places :)
JavaScript
const jsearch = require('/MarkLogic/jsearch.sjs');
const geoSamples = jsearch.collections('geo-example');
const matchedPlaces = [];

geoSamples.documents()
  .where(cts.elementChildGeospatialQuery(
    fn.QName('http://www.opengis.net/kml/2.2', 'Point'),
    fn.QName('http://www.opengis.net/kml/2.2', 'coordinates'),
    cts.circle(1, cts.point(37.5073428,-122.2465038)),
    ['type=long-lat-point']
  ))
  .result().results.forEach(function(result) {
    // extract just the names of the matched places
    matchedPlaces.push(
      result.document.xpath('//*:name/fn:data()'))
  });
matchedPlaces

(The above queries were written for sample documents containing KML geospatial data, so an element child query is used to confine matches to coordinates in KML <Point/> elements. The long-lat point type is used because KML coordinates are expressed in longitude-first order.)

The MarkLogic APIs also include geospatial utility functions useful for constructing criteria and analyzing search matches. For example, you can use the cts:region-contains XQuery function or the cts.region-contains JavaScript function to test whether one region contains another. The utility functions are usable with in-memory geospatial data, as well as data in documents in the database. For details, see Summary of Other Geospatial Operations.

Example: Point Query Using XQuery

This example uses XQuery to demonstrate the following type of point queries:

  • Find documents containing this point
  • Find documents containing points in this region

For an equivalent Server-Side JavaScript example, see Example: Point Query Using JavaScript. The example assumes the data and database configuration from Preparing to Run the Examples.

The sample data is XML documents containing KML data of the following form. For more details on the sample documents, see Overview of the Sample Data.

<envelope>
  <Placemark xmlns="http://www.opengis.net/kml/2.2">
    <name>MarkLogic HQ</name>
    <Point>
      <coordinates>-122.2465038,37.5073428</coordinates>
    </Point>
  </Placemark>
</envelope>

The example uses cts:element-child-geospatial-query to find matches in coordinates element of a KML Point element. Limiting the scope to coordinates in a Point element prevents false positives from the documents containing other kinds of regions. For example:

cts:element-child-geospatial-query(
      fn:QName("http://www.opengis.net/kml/2.2", "Point"),
      fn:QName("http://www.opengis.net/kml/2.2", "coordinates"),
      cts:point(37.5073428, -122.2465038), 
      ("type=long-lat-point")

The query includes the 'type=long-lat-point' option because KML uses longitude-first coordinate order while the default in MarkLogic is latitude-first ("type=point").

The database configuration includes a corresponding geospatial element child index on kml:Point/kml:coordinates with long-lat point type.

The following code performs one search for documents containing the coordinates of the MarkLogic headquarters (cts:point(37.5073428, -122.2465038)) and one search for documents containing points in the 'MarkLogic Neighborhood' polygon. The polygon coordinates are extracted from one of the sample documents, but you could also construct them inline.

xquery version "1.0-ml";

(: Find docs containing a point that matches another point.
 : The criteria point corresponds to the MarkLogic HQ feature :)
let $point-matches :=
  cts:search(fn:collection("geo-xml-examples"),
    cts:element-child-geospatial-query(
      fn:QName("http://www.opengis.net/kml/2.2", "Point"),
      fn:QName("http://www.opengis.net/kml/2.2", "coordinates"),
      cts:point(37.5073428, -122.2465038), 
      ("type=long-lat-point")
    )
  )
(: Find docs containing a point contained in a region. The
 : MarkLogic Neighborhood polygon is used as the criteria region. :)
let $region-matches :=
  cts:search(fn:collection("geo-xml-examples"),
    cts:element-child-geospatial-query(
      fn:QName("http://www.opengis.net/kml/2.2", "Point"),
      fn:QName("http://www.opengis.net/kml/2.2", "coordinates"),
      fn:doc("/geo-examples/MarkLogic-Neighborhood.xml")//cts-region, 
      ("type=long-lat-point")
    )
  )
(: Format results for display in QC :)
return (
  fn:concat("Features containing the critieria point: ", 
            fn:string-join($point-matches//*:name/data(), ", ")),
  fn:concat("Features containing points in the criteria region: ", 
            fn:string-join($region-matches//*:name/data(), ", "))
)

If you run this query in Query Console, it produces output similar to the following:

Features containing the critieria point: MarkLogic HQ
Features containing points in the criteria region: 
   Restaurant, Museum, MarkLogic HQ

You can compose complex queries by combining geospatial queries with other query types. For example, the following code matches documents that contains points within a circle and that also contain the word 'MarkLogic':

cts:search(fn:collection("geo-xml-examples"),
  cts:and-query((
    cts:word-query("MarkLogic"),
    cts:element-child-geospatial-query(
      fn:QName("http://www.opengis.net/kml/2.2", "Point"),
      fn:QName("http://www.opengis.net/kml/2.2", "coordinates"),
      cts:circle(1, cts:point(37.5073428,-122.2465038)),
      ("type=long-lat-point")
    )
  ))
)//*:name/data()

Though the previous examples only searched the XML sample documents, you can apply a geospatial query to either XML or JSON documents, or both. For example, the following code searches both the XML and JSON sample documents by combining two geospatial queries in an OR query. (The point search criteria matches the 'MarkLogic HQ' feature in the sample documents.)

let $matches :=
  cts:search(fn:collection("geo-examples"),
    cts:or-query((
      cts:path-geospatial-query(
        '//geometry[type = "Point"]/array-node("coordinates")',
        cts:point(37.5073428, -122.2465038),
        ('type=long-lat-point')
      ),
      cts:element-child-geospatial-query(
        fn:QName('http://www.opengis.net/kml/2.2', 'Point'),
        fn:QName('http://www.opengis.net/kml/2.2', 'coordinates'),
        cts:point(37.5073428, -122.2465038), 
        ('type=long-lat-point')
      )
    ))
  )
return
  for $match in ($matches) 
  return xdmp:node-uri($match)

Running this query in Query Console produces the following output:

/geo-examples/MarkLogic-HQ.json
/geo-examples/MarkLogic-HQ.xml

Example: Point Query Using JavaScript

This example uses Server-Side JavaScript to demonstrate the following type of point queries:

  • Find documents containing this point
  • Find documents containing points in this region

For an equivalent XQuery example, see Example: Point Query Using XQuery. This example assumes the data and database configuration from Preparing to Run the Examples.

The sample data includes JSON documents containing GeoJSON data of the following form. For more details on the sample documents, see Overview of the Sample Data.

{ "envelope": {
  "feature": {
    "type": "Feature", 
    "geometry": {
      "type": "Point", 
      "coordinates": [ -122.2465038, 37.5073428 ]
    }, 
    "properties": { "name": "MarkLogic HQ" }
} } }

The example uses cts.pathGeospatialQuery to find matching documents. You must use a path query for point queries on GeoJSON for the reasons described in Geospatial Data in the Application Developer's Guide. The following path addresses the coordinates array of a point feature in the sample documents:

//geometry[type = "Point"]/array-node("coordinates")

Thus, the core of the search is a path query of the following form. The query includes the 'type=long-lat-point' option because GeoJSON uses longitude-first coordinate order while the default in MarkLogic is latitude-first ("type=point").

cts.pathGeospatialQuery(
  '//geometry[type = "Point"]/array-node("coordinates")',
  cts.circle(0.25, cts.point(37.5073428, -122.2465038)), 
  ("type=long-lat-point")
)

The database configuration must include a corresponding geospatial path index. The instructions in Preparing to Run the Examples include creating a suitable index.

The following code uses the JSearch API to perform 2 searches: one search for documents containing a point (cts.point(37.5073428, -122.2465038)), and one for documents containing points in a region. The region coordinates are extracted from one of the sample documents for convenience, but you could also construct the region inline using a geospatial constructor such as cts.polygon.

'use strict';
const jsearch = require('/MarkLogic/jsearch.sjs');
const geoSamples = jsearch.collections('geo-examples');

// Find docs containing a point that matches another point.
// The criteria point corresponds the MarkLogic HQ feature.
const pointMatches =
  geoSamples.documents().where(
    cts.pathGeospatialQuery(
      '//geometry[type = "Point"]/array-node("coordinates")',
      cts.point(37.5073428, -122.2465038), 
      ("type=long-lat-point")
    )
  ).map(desc => desc.document.envelope.feature.properties.name)
  .result();
const regionMatches =
  geoSamples.documents().where(
    cts.pathGeospatialQuery(
      '//geometry[type = "Point"]/array-node("coordinates")',
      fn.head(fn.doc('/geo-examples/MarkLogic-Neighborhood.json'))
        .toObject().envelope.ctsRegion,
      ["type=long-lat-point"]
    )
  ).map(desc => desc.document.envelope.feature.properties.name)
  .result();

// Format the results for display.
const results = 'Features containing the critieria point: ' 
  + pointMatches.results.join(", ")
  + '\nFeatures containing points in the criteria region: '
  + regionMatches.results.join(', ');
results;

If you run this query in Query Console, it produces output similar to the following:

Features containing the critieria point: MarkLogic HQ
Features containing points in the criteria region: 
   Restaurant, Museum, MarkLogic HQ

Note that a lambda expression and the map method are used to extract just the feature names from the matched documents:

geoSamples.documents()
    .where(...)
    .map(desc => desc.document.envelope.feature.properties.name)
    .result();

This is a contrivance used to keep the example output brief. If you remove the map call, the search returns a Sequence of document descriptors that include the full document. For more details, see Creating JavaScript Search Applications.

You can also use cts.search to perform an equivalent search. For example:

// Find docs containing a point that matches another point.
// The criteria point corresponds the MarkLogic HQ feature.
const pointMatches =
  cts.search(
    cts.andQuery([
      cts.collectionQuery('geo-json-examples'),
      cts.pathGeospatialQuery(
        '//geometry[type = "Point"]/array-node("coordinates")',
        cts.point(37.5073428, -122.2465038), 
        ('type=long-lat-point')
    )
    ])
  );
// Find docs containing points contained in a region. The MarkLogic
// Neighborhood polygon is used as the criteria to be matched.
const regionMatches =
  cts.search(
    cts.andQuery([
      cts.collectionQuery('geo-json-examples'),
      cts.pathGeospatialQuery(
        '//geometry[type = "Point"]/array-node("coordinates")',
        fn.head(fn.doc('/geo-examples/MarkLogic-Neighborhood.json'))
          .toObject().envelope.ctsRegion,
        ['type=long-lat-point']
      )
    ])
  );

// Format the results for display.
const results = 'Features containing the critieria point: ';
const featureNames = [];
for (let doc of pointMatches) {
  featureNames.push(doc.toObject().envelope.feature.properties.name);
}
results += featureNames.join(', ');

results += '\nFeatures containing points in the criteria region: ';
featureNames = [];
for (let doc of regionMatches) {
  featureNames.push(doc.toObject().envelope.feature.properties.name);
}
results + featureNames.join(', ');

You can include multiple criteria in a single query; when you do so, encapsulate the criteria in an array. For example, you could search for matches to both the point and the region with a query such as the following. A document matches if it matches any one of the criteria

cts.andQuery([
  cts.collectionQuery('geo-json-examples'),
  cts.pathGeospatialQuery(
    '//geometry[type = "Point"]/array-node("coordinates")',
    [cts.point(37.5073428, -122.2465038), 
     fn.head(fn.doc('/geo-examples/MarkLogic-Neighborhood.json'))
       .toObject().envelope.ctsRegion],
    ['type=long-lat-point']
) ])

Since the MarkLogic HQ feature document satisfies both critieria and the Museum and Restuarant feature documents satisfy the region criteria, the above query matches the Restaurant, Museum, and MarkLogic HQ features.

You can compose complex queries by combining geospatial queries with other query types. Notice that the above cts.search example search uses a cts.andQuery to combine the geospatial path query with a collection query that constrains the search to the JSON documents in the sample set.

To include the XML sample documents in the search, add a cts.elementChildGeospatialQuery on the KML data. For example, the following query finds documents containing the MarkLogic HQ coordinates in either the XML or JSON sample documents, and prints out the URIs of the matched documents:

'use strict';
const jsearch = require('/MarkLogic/jsearch.sjs');
const geoSamples = jsearch.collections('geo-examples');

geoSamples.documents().where(
  cts.orQuery([
    cts.pathGeospatialQuery(
      '//geometry[type = "Point"]/array-node("coordinates")',
      cts.point(37.5073428, -122.2465038),
      ['type=long-lat-point']
    ),
    cts.elementChildGeospatialQuery(
      fn.QName('http://www.opengis.net/kml/2.2', 'Point'),
      fn.QName('http://www.opengis.net/kml/2.2', 'coordinates'),
      cts.point(37.5073428, -122.2465038), 
      ['type=long-lat-point']
    )
  ])
).map(desc => desc.uri)
 .result().results;

Running the above query in Query Console, produces the following output:

["/geo-examples/MarkLogic-HQ.json", 
 "/geo-examples/MarkLogic-HQ.xml"]

Constructing a Point Query in XQuery

This section is a quick reference of available XQuery geospatial point query constructors. These functions create a cts:query object. For an equivalent JavaScript reference, see Constructing a Point Query in JavaScript. To create geospatial queries from query text, see Constructing a Point Query from Query Text.

Use the following functions to construct a point query. Select the query constructor that corresponds to the type of region and layout of the data to be searched, as described in Understanding Geospatial Query and Index Types. You can use these constructors with each other and with other cts:query constructors to build up complex queries.

Every query constructor includes parameters that identify the content to search, either by path, name, or index reference; and one or more geospatial values to match. For example:

cts:element-geospatial-query(
  xs:QName("feature"),                         (: element to search :)
  cts:circle(20, cts:point(37.65, -122.42))    (: criteria :)
)

For a complete example, see Example: Point Query Using XQuery. For more details about constructing geospatial search criteria, see Constructing Geospatial Point and Region Values and Converting To and From Common Geospatial Representations.

Constructing a Point Query in JavaScript

This section is a quick reference of available Server-Side JavaScript geospatial point query constructors. These functions create a cts.query object. For an equivalent XQuery reference, see Constructing a Point Query in XQuery. To create geospatial queries from query text, see Constructing a Point Query from Query Text.

The following JavaScript geospatial query constructors are available. You can use these constructors with each other and with other cts:query constructors to build up complex queries. Select the query constructor that corresponds to the type of region and layout of the data to be searched, as described in Understanding Geospatial Query and Index Types.

Every query constructor includes parameters that identify the content to search, either by path, name, or index reference; and one or more geospatial values to match. For example:

cts.elementGeospatialQuery(
  "feature",                                   // element to search
  cts.circle(20, cts.point(37.65, -122.42))    // criteria
)

For a complete example, see Example: Point Query Using XQuery. For more details about constructing geospatial search criteria, see Constructing Geospatial Point and Region Values.

Constructing a Point Query from Query Text

You can use the cts:parse XQuery function or the cts.parse JavaScript function to create a geospatial point query from query text. The parse creates a cts query object. This grammar is only supported by the cts parser; the grammar used by search:search or search:resolve does not support geospatial terms.

The cts parse grammar supports search terms expressing points, circles, boxes, polygons, and other regions, bound a geospatial index reference. For details, see Binding to a Geospatial Index Reference.

The following example queries create a geospatial element child query over KML point coordinates. The bindings define the interpretation of the 'poi' (point of interest) tag as a reference to a geospatial element child index. The query text '@1 -122.2465038,37.5073428' represents a circle with radius 1 mile (the default units) and center (37.5073428, -122.2465038). The query includes the option 'type=long-lat-point' because KML uses longitude-first ordering for points, while the MarkLogic default ordering is latitude-first.

LanguageExample
XML
xquery version "1.0-ml";
let $bindings := map:map()
let $_ := map:put(
  $bindings, "poi", 
  cts:geospatial-element-child-reference(
    fn:QName("http://www.opengis.net/kml/2.2", "Point"),
    fn:QName("http://www.opengis.net/kml/2.2", "coordinates"),
    ("type=long-lat-point")
  ) )
return 
  cts:parse('poi:"@1 -122.2465038,37.5073428"', $bindings)
JavaScript
const bindings = {
  'poi': cts.geospatialElementChildReference(
    fn.QName("http://www.opengis.net/kml/2.2", "Point"),
    fn.QName("http://www.opengis.net/kml/2.2",
             "coordinates"),
    ["type=long-lat-point"])
};
cts.parse('poi:"@1 -122.2465038,37.5073428"', bindings);

The parse produces a cts query similar to the following:

LanguageExample Output
XQuery
cts:element-child-geospatial-query(
  fn:QName("http://www.opengis.net/kml/2.2","Point"), 
  fn:QName("http://www.opengis.net/kml/2.2","coordinates"), 
  cts:circle("@1 -122.2465,37.507343"), 
  ("type=long-lat-point"), 1)
JavaScript
cts.elementChildGeospatialQuery(
  [fn.QName("http://www.opengis.net/kml/2.2","Point")],
  [fn.QName("http://www.opengis.net/kml/2.2","coordinates")],
  cts.circle("@1 -122.2465,37.507343"),
  ["type=long-lat-point"], 1)

For more details, see Creating a Query From Search Text With cts:parse.

Creating Point Queries with the Client APIs

The REST, Java, and Node.js Client APIs expose geospatial queries through the use of structured queries and query builders, rather than through standalone query constructor functions.

The following topics provide examples of using a point query from the Client APIs:

You can also use a serialized cts query or a structured query with the Node.js, Java, or REST Client APIs and Search API functions such as search:resolve. See the following topics for details on including a point query in a structured query:

Java Client API

This topic assumes you are already familiar with the search features of the Java Client API. If you are not, see the Java Application Developer's Guide.

You are most likely to construct a geospatial point query with the Java Client API using a StructuredQueryBuilder object. You could also embed a structured point query in a RawCombinedQuery; this technique is not covered here. You cannot create a geospatial point query in Java using query text or QBE.

Each geospatial point query can only reference a single point index. To search more than one index, construct multiple point queries and combine them with an OR query.

Use StructuredQueryDefinition.geospatial to create a point query. Choose an overload that accepts a GeospatialIndex as input. A GeospatialIndex object identifies the point index to be searched.

To construct a GeospatialIndex object, use one of the geospatial index builders of StructuredQueryBuilder, such as StructuredQueryBuilder.geoElement. Choose the index builder that matches your index and data layout; for details, see Understanding Geospatial Query and Index Types.

For example, the following code snippet identifies a geospatial element child point index corresponding to the KML Point features in the data from Preparing to Run the Examples.

DatabaseClient client = ...;
QueryManager qm = client.newQueryManager();
StructuredQueryBuilder sqb = qm.newStructuredQueryBuilder();
...
sqb.geoElement(
  sqb.element(new QName("http://www.opengis.net/kml/2.2", "Point")),
  sqb.element(
    new QName("http://www.opengis.net/kml/2.2", "coordinates"))),
...

The following example uses the Java Client API to find XML documents containing the feature named 'MarkLogic HQ'.

package examples;

import com.marklogic.client.DatabaseClient;
import com.marklogic.client.DatabaseClientFactory;
import com.marklogic.client.io.SearchHandle;
import com.marklogic.client.query.MatchDocumentSummary;
import com.marklogic.client.query.QueryManager;
import com.marklogic.client.query.StructuredQueryBuilder;
import com.marklogic.client.query.StructuredQueryBuilder.FragmentScope;
import com.marklogic.client.query.StructuredQueryDefinition;

import javax.xml.namespace.QName;


public class GeoPointQuery {
    public static void main(String[] args) {
public static void main(String[] args) {
    // MODIFY THIS CALL TO MATCH YOUR ENV
    DatabaseClient client = DatabaseClientFactory.newClient(
        hostname, port, databaseName,
        new DatabaseClientFactory.DigestAuthContext(
            username, password));

    QueryManager qm = client.newQueryManager();
    StructuredQueryBuilder sqb = qm.newStructuredQueryBuilder();
    SearchHandle results = new SearchHandle();
    StructuredQueryDefinition query = sqb.geospatial(
        sqb.geoElement(
            sqb.element(new QName("http://www.opengis.net/kml/2.2",
                        "Point")),
            sqb.element(new QName("http://www.opengis.net/kml/2.2",
                        "coordinates"))),
        FragmentScope.DOCUMENTS, 
        new String[] {"type=long-lat-point"},
        sqb.circle(37.507, -122.246, 0.25));
    qm.search(query, results);
    for (MatchDocumentSummary match : results.getMatchResults()) {
        System.out.println(match.getUri());
    }

    client.release();
}
}

If you run the above program against the sample data and database configuration from Preparing to Run the Examples, you should see output similar to the following:

/geo-examples/MarkLogic-HQ.xml

The example as written will only match the XML sample documents from Preparing to Run the Examples. You can match the JSON sample documents by changing the index builder to use StructuredQueryBuilder.geoPath and the path //geometry[type = "Point"]/array-node("coordinates"), similar to the example in Example: Point Query Using JavaScript.

For more details, see Searching in the Java Application Developer's Guide.

Node.js Client API

This topic assumes you are familiar with the search features of the Node.js Client API. If you are not, you should review the Node.js Application Developer's Guide.

To construct a geospatial point query, use queryBuilder.geospatial. Use one of the geospatial index reference builders such as queryBuilder.geoPath or queryBuilder.geoElement to construct the point index specification. Choose the builder that corresponds to your index and data layout, as described in Understanding Geospatial Query and Index Types. Use helper functions such as queryBuilder.point to construct the criteria point(s) or regions(s).

Each geospatial point query can only reference a single index. To search more than one index, construct multiple region queries and combine them with an OR query.

The following example performs the same search as Example: Simple Intersection Region Query. The example relies on the sample documents and database configuration from Preparing to Run the Examples. Before running the example, modify the connection information in connInfo.

const marklogic = require('marklogic');

// MODIFY THIS VAR TO MATCH YOUR ENV
const connInfo = {
    host: 'localhost',
    port: 8000,
    user: username,
    password: password,
    database: 'Documents'
  };
const db = marklogic.createDatabaseClient(connInfo);
const qb = marklogic.queryBuilder;

db.documents.query(
  qb.where(
    qb.geospatial(
      qb.geoElement(
        qb.qname('http://www.opengis.net/kml/2.2', 'Point'),
        qb.qname('http://www.opengis.net/kml/2.2', 'coordinates')),
      qb.fragmentScope('documents'),
      qb.geoOptions('type=long-lat-point'),
      qb.circle(0.25, qb.latlon(37.507, -122.246))
   ))
).result( function(results) {
  for (let result of results) {
    console.log(result.uri);
  }
});

If you run the example against the sample data from Preparing to Run the Examples, you should see output similar to the following:

/geo-examples/MarkLogic-HQ.xml

As written, the sample code will only match the XML documents in the sample data. To match the JSON documents, use queryBuilder.geoPath instead of queryBuilder.geoElement and the path //geometry[type = "Point"]/array-node("coordinates"), similar to the example in Example: Point Query Using JavaScript.

For more details, see Querying Documents and Metadata in the Node.js Application Developer's Guide.

Searching for Matching Regions

This section describes how to use a region query to search for regions in your documents. You can match regions using topological operators such as as containment and intersection.

This section covers the following topics:

Region Match Overview

A region query matches geospatial regions in your documents against one or more criteria regions. The relationship between the regions that must be satisfied for a 'match' is determined by the operator configured into the query. For example, you can create a query that matches documents containing a region that overlaps or intersects your criteria region(s).

To construct a region query, use the XQuery cts:geospatial-region-query function or the JavaScript cts.geospatialRegionQuery function. Region queries require a geospatial region path index.

For example, the following code snippet creates a query the matches documents containing a region the intersects a circle. For a complete example, see Example: Simple Intersection Region Query.

LanguageExample
XQuery
cts:geospatial-region-query(
  cts:geospatial-region-path-reference("/envelope/cts-region"),
  "intersects", 
  cts:circle(0.25, cts:point(37.507343,-122.2465))
)
JavaScript
cts.geospatialRegionQuery(
  cts.geospatialRegionPathReference('/envelope/ctsRegion'),
  'intersects', 
  cts.circle(0.25, cts.point(37.507343,-122.2465))
)

The following are key points about using region queries:

The MarkLogic APIs also include geospatial utility functions useful for constructing criteria and analyzing search matches. For example, you can use the cts:region-contains XQuery function or the cts.region-contains JavaScript function to test whether one region contains another. The utility functions are usable with in-memory geospatial data, as well as data in documents in the database. For details, see Summary of Other Geospatial Operations.

Example: Simple Intersection Region Query

This example depends on the data and configuration in Preparing to Run the Examples.

Run one of the following queries in Query Console to find documents that contain a region that intersects with a polygon. The criteria polygon corresponds to the 'MarkLogic Neighborhood' region in the sample data. The query produces the feature names from the matched documents.

LanguageExample
XQuery
xquery version "1.0-ml";
declare namespace kml="http://www.opengis.net/kml/2.2";

(: This region corresponds to the MarkLogic Neighborhood polygon :)
let $criteria-region := cts:polygon((
  cts:point(37.519087, -122.26346), 
  cts:point(37.521299, -122.24805), 
  cts:point(37.512279, -122.24462), 
  cts:point(37.50336,  -122.24556),
  cts:point(37.506185, -122.25981), 
  cts:point(37.513436, -122.26337), 
  cts:point(37.519087, -122.26346)
))
return cts:search(
  fn:collection("geo-xml-examples"),
  cts:geospatial-region-query(
    cts:geospatial-region-path-reference("/envelope/cts-region"),
    "intersects", $criteria-region
  )
)/envelope/kml:Placemark/kml:name/fn:data()
JavaScript
// This region corresponds to the MarkLogic Neighborhood polygon
const criteriaRegion = cts.polygon([
  cts.point(37.519087, -122.26346), 
  cts.point(37.521299, -122.24805), 
  cts.point(37.512279, -122.24462), 
  cts.point(37.50336,  -122.24556),
  cts.point(37.506185, -122.25981), 
  cts.point(37.513436, -122.26337), 
  cts.point(37.519087, -122.26346)
])

// Perform the search
const results = cts.search(cts.andQuery([
  cts.collectionQuery('geo-json-examples'),
    cts.geospatialRegionQuery(
    cts.geospatialRegionPathReference('/envelope/ctsRegion'),
    'intersects', criteriaRegion
  )
]))

// Iterate over the results, accumulating the feature names in
// an array for convenient display in Query Console.
const matchedRegions = [];
for (let result of results) {
  matchedRegions.push(
    result.toObject().envelope.feature.properties.name)
}
matchedRegions

If you run one of these queries in Query Console, the following feature names should be displayed:

  • Holly St
  • Wildlife Refuge
  • Hwy 101
  • Airport
  • MarkLogic Neighborhood

You can also work with XML documents in JavaScript and work with JSON documents in XQuery, as shown below. The following example performs the same region search against the 'opposite' document type.

LanguageExample
XQuery
xquery version "1.0-ml";
declare namespace kml="http://www.opengis.net/kml/2.2";

(: This region corresponds to the MarkLogic Neighborhood polygon :)
let $criteria-region := cts:polygon((
  cts:point(37.519087, -122.26346), 
  cts:point(37.521299, -122.24805), 
  cts:point(37.512279, -122.24462), 
  cts:point(37.50336,  -122.24556),
  cts:point(37.506185, -122.25981), 
  cts:point(37.513436, -122.26337), 
  cts:point(37.519087, -122.26346)
))
return cts:search(
  fn:collection("geo-json-examples"),
  cts:geospatial-region-query(
    cts:geospatial-region-path-reference("/envelope/ctsRegion"),
    "intersects", $criteria-region
  )
)/envelope/feature/properties/name
JavaScript
// This region corresponds to the MarkLogic Neighborhood polygon
const criteriaRegion = cts.polygon([
  cts.point(37.519087, -122.26346), 
  cts.point(37.521299, -122.24805), 
  cts.point(37.512279, -122.24462), 
  cts.point(37.50336,  -122.24556),
  cts.point(37.506185, -122.25981), 
  cts.point(37.513436, -122.26337), 
  cts.point(37.519087, -122.26346)
])

// Perform the search
const results = cts.search(cts.andQuery([
  cts.collectionQuery('geo-xml-examples'),
    cts.geospatialRegionQuery(
    cts.geospatialRegionPathReference('/envelope/cts-region'),
    'intersects', criteriaRegion
  )
]))

// Iterate over the results, accumulating the feature names in
// an array for convenient display in Query Console.
const matchedRegions = [];
for (let result of results) {
  matchedRegions.push(fn.head(result.xpath(
    '/envelope/kml:Placemark/kml:name/fn:data()',
    {kml: 'http://www.opengis.net/kml/2.2'})))
}
matchedRegions

Notice that the result processing in JavaScript is significantly different because you cannot handle the matched XML documents as native javascript objects.

The following example searches the combined set of both XML and JSON documents. Notice that you can pass multiple region index references into the geospatial region query constructor. A document satisfies the query if a match is found using any of the indexes.

LanguageExample
XQuery
xquery version "1.0-ml";

(: This region corresponds to the MarkLogic Neighborhood polygon :)
let $criteria-region := cts:polygon((
  cts:point(37.519087, -122.26346), 
  cts:point(37.521299, -122.24805), 
  cts:point(37.512279, -122.24462), 
  cts:point(37.50336,  -122.24556),
  cts:point(37.506185, -122.25981), 
  cts:point(37.513436, -122.26337), 
  cts:point(37.519087, -122.26346)
))
let $matches := cts:search(
  fn:collection("geo-examples"),
  cts:geospatial-region-query(
    (cts:geospatial-region-path-reference("/envelope/cts-region"),
     cts:geospatial-region-path-reference("/envelope/ctsRegion")),
    "intersects", $criteria-region
  )
)
for $doc in $matches order by xdmp:node-uri($doc)
return xdmp:node-uri($doc)
JavaScript
// This region corresponds to the MarkLogic Neighborhood polygon
const criteriaRegion = cts.polygon([
  cts.point(37.519087, -122.26346), 
  cts.point(37.521299, -122.24805), 
  cts.point(37.512279, -122.24462), 
  cts.point(37.50336,  -122.24556),
  cts.point(37.506185, -122.25981), 
  cts.point(37.513436, -122.26337), 
  cts.point(37.519087, -122.26346)
]);

// Perform the search
const results = cts.search(cts.andQuery([
  cts.collectionQuery('geo-examples'),
  cts.geospatialRegionQuery(
    [cts.geospatialRegionPathReference('/envelope/cts-region'),
     cts.geospatialRegionPathReference('/envelope/ctsRegion')],
    'intersects', criteriaRegion
  )
]));

// Accumulate the matched URIs in an array for
// convenient display of brief results in Query Console.
const matchedRegions = [];
for (let result of results) {
  matchedRegions.push(xdmp.nodeUri(result))
}
matchedRegions.sort()

If you run one of these queries in Query Console, it emits the following list of URIs:

/geo-examples/Airport.json
/geo-examples/Airport.xml
/geo-examples/Holly-St.json
/geo-examples/Holly-St.xml
/geo-examples/Hwy-101.json
/geo-examples/Hwy-101.xml
/geo-examples/MarkLogic-Neighborhood.json
/geo-examples/MarkLogic-Neighborhood.xml
/geo-examples/Wildlife-Refuge.json
/geo-examples/Wildlife-Refuge.xml

Example: Using Region Queries in a Composed Query

This example depends on the data and configuration in Preparing to Run the Examples.

You can use geospatial region queries along with other query types to compose more complex queries. For example, the following queries find documents containing a region that intersects with the 'MarkLogic Neighborhood' region, but that do not contain a region that is covered by the 'MarkLogic Neighborhood' region.

LanguageExample
XQuery
xquery version "1.0-ml";
declare namespace kml="http://www.opengis.net/kml/2.2";

(: This region corresponds to the MarkLogic Neighborhood polygon :)
let $criteria-region := cts:polygon((
  cts:point(37.519087, -122.26346), 
  cts:point(37.521299, -122.24805), 
  cts:point(37.512279, -122.24462), 
  cts:point(37.50336,  -122.24556),
  cts:point(37.506185, -122.25981), 
  cts:point(37.513436, -122.26337), 
  cts:point(37.519087, -122.26346)
))
return cts:search(
  fn:collection("geo-example"),
  cts:and-not-query(
    cts:geospatial-region-query(
      cts:geospatial-region-path-reference("/envelope/cts-region"),
       "intersects", $criteria-region
    ),
    cts:geospatial-region-query(
      cts:geospatial-region-path-reference("/envelope/cts-region"),
        "covered-by", $criteria-region
    )
  )
)/envelope/kml:Placemark/kml:name/fn:data()
JavaScript
const criteriaRegion = cts.polygon([
  cts.point(37.519087, -122.26346), 
  cts.point(37.521299, -122.24805), 
  cts.point(37.512279, -122.24462), 
  cts.point(37.50336,  -122.24556),
  cts.point(37.506185, -122.25981), 
  cts.point(37.513436, -122.26337), 
  cts.point(37.519087, -122.26346)
]);

// Perform the search
const results = cts.search(cts.andQuery([
  cts.collectionQuery('geo-example'),
  cts.andNotQuery(
    cts.geospatialRegionQuery(
      cts.geospatialRegionPathReference('/envelope/cts-region'),
       'intersects', criteriaRegion
    ),
    cts.geospatialRegionQuery(
      cts.geospatialRegionPathReference('/envelope/cts-region'),
        'covered-by', criteriaRegion
    )
  )
]));

// Iterate over the results. We accumulate the feature names in
// an array just for convenient display in Query Console.
const matchedRegions = [];
for (let result of results) {
  matchedRegions.push(fn.head(result.xpath(
    '/envelope/kml:Placemark/kml:name/fn:data()',
    {kml: 'http://www.opengis.net/kml/2.2'})))
}
matchedRegions

Query Console displays the following feature names if the query is successful:

Wildlife Refuge
Hwy 101

Constructing a Region Query Using a Constructor

This section demonstrates how to use the constructor functions cts:geospatial-region-query (XQuery) or cts.geospatialRegionQuery (JavaScript) to construct a region query on MarkLogic Server. You can also construct a region query from query text using cts:parse (XQuery) or cts.parse (JavaScript); for details, see Constructing a Region Query from Query Text.

A region query has the following form:

XQueryJavaScript
cts:geospatial-region-query(
  $region-index-references,
  $operator,
  $criteria-regions,
  $options,
  $weight)
cts.geospatialRegionQuery(
  [regionIndexReference, ...],
  operator,
  [criteriaRegion, ...],
  [option, ...],
  weight)

The options and weight parameters are optional. You can specify multiple region indexes and multiple criteria regions, which is treated as an implicit OR query. That is, a document matches if it satisfies any of the comparisons.

A region query must be backed by a corresponding geospatial region path index. For more details, see Geospatial Region Queries and Indexes.

For example, the following constructor creates a region query that matches region data located at the XPath '/envelope/cts-region' the overlap with a circle with center (-122.2465,37.507343) and radius 1 mile.

LanguageExample Output
XQuery
cts:geospatial-region-query(
  cts:geospatial-region-path-reference("/envelope/cts-region"),
  "overlaps", 
  cts:circle("@1 -122.2465,37.507343"))
JavaScript
cts.geospatialRegionQuery(
  [cts.geospatialRegionPathReference("/envelope/cts-region")],
  "overlaps", 
  cts.circle("@1 -122.2465,37.507343"))

The operator must be a string with one of the following values, corresponding to the DE9-IM predicates.

  • contains
  • covered-by
  • covers
  • crosses
  • disjoint
  • equals
  • intersects
  • overlaps
  • touches
  • within

You can construct your criteria regions using region constructors such as the cts:polygon (XQuery) or cts.polygon (JavaScript), the geospatial format conversion functions such as geogml:linestring (XQuery) or geojson.circle (JavaScript), or using WKT or WKB.

For more details, see the following topics:

Constructing a Region Query from Query Text

You can use the cts:parse XQuery function or the cts.parse JavaScript function to create a geospatial region query from query text. If you bind a tag to a geospatial region path index, then you can use the tag in an expression of the following form in your query text:

boundTag operator criteriaRegion [options]

For example, if 'reg' is the name of a tag bound to a geospatial region index, then the following expression parses to a geospatial region query than matches regions in your documents that overlap with the given polygon

reg DE9IM_OVERLAPS POLYGON((1 1,2 2,0 1,1 1))

The operator must be one of the following. For more details on the operators, see Operators Usable with Geospatial Queries and http://en.wikipedia.org/wiki/DE-9IM.

  • DE9IM_CONTAINS
  • DE9IM_COVERED_BY
  • DE9IM_COVERS
  • DE9IM_CROSSES
  • DE9IM_DISJOINT
  • DE9IM_EQUALS
  • DE9IM_INTERSECTS
  • DE9IM_OVERLAPS
  • DE9IM_TOUCHES
  • DE9IM_WITHIN

The following example queries illustrate the tag binding and parsing necessary to search using this query text. The binding specifies the tag 'reg' represents a geospatial region path index reference for the XPath expression '/envelope/cts-region'. The query text '@1 -122.2465038,37.5073428' represents a circle with radius 1 mile (the default units) and center (37.5073428, -122.2465038).

LanguageExample
XML
xquery version "1.0-ml";
let $bindings := map:map()
let $_ := map:put(
  $bindings, "reg", 
  cts:geospatial-region-path-reference(
    "/envelope/cts-region")
)
return cts:parse(
  'reg DE9IM_OVERLAPS "@1 -122.2465038,37.5073428"',
  $bindings
)
JavaScript
const bindings = {
  'reg': cts.geospatialRegionPathReference(
    '/envelope/cts-region')
};
cts.parse(
  'reg DE9IM_OVERLAPS "@1 -122.2465038,37.5073428"', 
  bindings);

The parse produces a cts query similar to the following:

LanguageExample Output
XQuery
cts:geospatial-region-query(
  (cts:geospatial-region-path-reference(
    "/envelope/cts-region",("coordinate-system=wgs84"))
  ), "overlaps", 
  cts:circle("@1 -122.2465,37.507343"), 
  (), 1)
JavaScript
cts.geospatialRegionQuery([
  cts.geospatialRegionPathReference(
      "/envelope/cts-region",
      ["coordinate-system=wgs84"])
    ], 
  "overlaps", 
  cts.circle("@1 -122.2465,37.507343"), 
  [], 1)

For more details, see Creating a Query From Search Text With cts:parse.

Creating Region Queries Using the Client APIs

See the following topics for an overview and example of using a region query in a search in the Client APIs.

JavaClient API

This topic assumes you are already familiar with the search features of the Java Client API. If you are not, see the Java Application Developer's Guide.

You are most likely to construct a geospatial region query with the Java Client API using the StructuredQueryBuilder. You could also embed a structured region query in a RawCombinedQuery; this technique is not covered here. You cannot create a geospatial region query in Java using query text or QBE.

Each geospatial region query can only reference a single region index. To search more than one index, construct multiple region queries and combine them with an OR query.

Use StructuredQueryDefinition.geospatial to create a region query. Choose an overload that accepts a GeospatialRegionIndex as input. A GeospatialRegionIndex object identifies the region index to be searched.

To construct a GeospatialRegionIndex object, use StructuredQueryBuilder.geoRegionPath. When defining the index, you must include a PathIndex value, and you may also include coordinate system and precision information.

For example, the following code snippet identifies a region index on the path /envelope/cts-region with the coordinate system 'wgs84'.

DatabaseClient client = ...;
QueryManager qm = client.newQueryManager();
StructuredQueryBuilder sqb = qm.newStructuredQueryBuilder();
...
sqb.geoRegionPath(
    sqb.pathIndex("/envelope/cts-region"), 
    StructuredQueryBuilder.CoordinateSystem.WGS84)
...

The following example uses the Java Client API to build a structured query equivalen to the query in Example: Simple Intersection Region Query. The example as written will only match the XML sample documents from Preparing to Run the Examples. You can match the JSON sample documents by changing the index path to /envelope/ctsRegion.

package examples;

import com.marklogic.client.DatabaseClient;
import com.marklogic.client.DatabaseClientFactory;
import com.marklogic.client.io.SearchHandle;
import com.marklogic.client.query.MatchDocumentSummary;
import com.marklogic.client.query.QueryManager;
import com.marklogic.client.query.StructuredQueryBuilder;
import com.marklogic.client.query.StructuredQueryBuilder.GeoSpatialOperator;
import com.marklogic.client.query.StructuredQueryDefinition;


public class GeoRegionQuery {
    public static void main(String[] args) {
        // MODIFY THIS CALL TO MATCH YOUR ENV
        DatabaseClient client = DatabaseClientFactory.newClient(
            hostname, port, databaseName,
            new DatabaseClientFactory.DigestAuthContext(
                username, password));

        QueryManager qm = client.newQueryManager();
        StructuredQueryBuilder sqb = qm.newStructuredQueryBuilder();
        SearchHandle results = new SearchHandle();
        
        StructuredQueryDefinition query = sqb.geospatial(
            sqb.geoRegionPath(
                sqb.pathIndex("/envelope/cts-region"), 
                StructuredQueryBuilder.CoordinateSystem.WGS84),
            GeospatialOperator.INTERSECTS, 
            sqb.polygon(
                sqb.point(37.519087, -122.26346),
                sqb.point(37.521299, -122.24805),
                sqb.point(37.512279, -122.24462),
                sqb.point(37.50336,  -122.24556),
                sqb.point(37.506185, -122.25981),
                sqb.point(37.513436, -122.26337),
                sqb.point(37.519087, -122.26346)
                        ));
        qm.search(query, results);
        for (MatchDocumentSummary match : results.getMatchResults()) {
            System.out.println(match.getUri());
        }

        client.release();
    }
}

If you run the above program against the sample data and database configuration from Preparing to Run the Examples, you should see output similar to the following:

/geo-examples/Hwy-101.xml
/geo-examples/Holly-St.xml
/geo-examples/Wildlife-Refuge.xml
/geo-examples/Shopping-Center.xml
/geo-examples/MarkLogic-Neighborhood.xml
/geo-examples/Airport.xml
Node.js Client API

This topic assumes you are familiar with the search features of the Node.js Client API. If you are not, you may want to review the Node.js Application Developer's Guide.

To construct a geospatial region query, use queryBuilder.geospatialRegion. You cannot create a region query using queryBuilder.parsedFrom or queryBuilder.byExample. Use queryBuilder.geoPath to construct the region index specification, and helper functions such as queryBuilder.polygon to construct the criteria region(s).

Each geospatial region query can only reference a single region index. To search more than one index, construct multiple region queries and combine them with an OR query.

The following example performs the same search as Example: Simple Intersection Region Query. The example relies on the sample documents and database configuration from Preparing to Run the Examples. Before running the example, modify the connection information in connInfo.

const marklogic = require('marklogic');

// MODIFY THIS VAR TO MATCH YOUR ENV
const connInfo = {
    host: 'localhost',
    port: 8000,
    user: username,
    password: password,
    database: 'Documents'
  };
const db = marklogic.createDatabaseClient(connInfo);
const qb = marklogic.queryBuilder;

db.documents.query(
  qb.where(
    qb.geospatialRegion(
      qb.geoPath('/envelope/ctsRegion', qb.coordSystem('wgs84')),
      'intersects',
      qb.polygon(
        qb.point(37.519087, -122.26346),
        qb.point(37.521299, -122.24805),
        qb.point(37.512279, -122.24462),
        qb.point(37.50336,  -122.24556),
        qb.point(37.506185, -122.25981),
        qb.point(37.513436, -122.26337),
        qb.point(37.519087, -122.26346))
   ))
).result( function(results) {
  for (let result of results) {
    console.log(result.uri);
  }
});

If you run the example against the sample data from Preparing to Run the Examples, you should see output similar to the following:

/geo-examples/Hwy-101.json
/geo-examples/Wildlife-Refuge.json
/geo-examples/Holly-St.json
/geo-examples/Airport.json
/geo-examples/Shopping-Center.json
/geo-examples/MarkLogic-Neighborhood.json

For more details, see the Node.js Application Developer's Guide.

REST Client API

This topic assumes you are already familiar with the search features of the REST Client API. If you are not, refer to the REST Application Developer's Guide.

To evaluate a geospatial region using the REST Client API, you can use either a cts:geospatial-region-query or a structured query that contains a geo-region-path-query or a geo-region-constraint-query. Your cts or structured query can be standalone or part of a combined query. You cannot construct query text or a QBE that represents a region query.

Each structured region query can reference only one region index. To search more than one region index at a time, create multiple region queries and combine them with an or-query.

The following example uses the REST Client API and a structured query to perform the same search as the one in Example: Simple Intersection Region Query. The example as written will only match the XML sample documents from Preparing to Run the Examples. You can match the JSON documents by changing the path-index to /envelope/ctsRegion.

Copy the following query into a file. You will use it the file as the POST body of a search request. The example curl command below assumes the filename is body.xml.

<query xmlns="http://marklogic.com/appservices/search">
  <geo-region-path-query coord="wgs84">
    <path-index>/envelope/cts-region</path-index>
    <geospatial-operator>intersects</geospatial-operator>
    <polygon>
      <point>
        <latitude>37.519087</latitude><longitude>-122.26346</longitude>
      </point>
      <point>
        <latitude>37.521299</latitude><longitude>-122.24805</longitude>
      </point>
      <point>
        <latitude>37.512279</latitude><longitude>-122.24462</longitude>
      </point>
      <point>
        <latitude>37.50336</latitude><longitude>-122.24556</longitude>
      </point>
      <point>
        <latitude>37.506185</latitude><longitude>-122.25981</longitude>
      </point>
      <point>
        <latitude>37.513436</latitude><longitude>-122.26337</longitude>
      </point>
      <point>
        <latitude>37.519087</latitude><longitude>-122.26346</longitude>
      </point>
    </polygon>
  </geo-region-path-query>
</query>

Run a curl command similar the following to perform the search. Before running the command, change the username and password. If you are not using the Document database as your content database, you will need to add a database request parameter to the URL.

curl --anyauth --user user:password -X POST -i \
  -d @./body.xml -H "Content-type: application/xml" \
  'http://localhost:8000/v1/search'

The search should match the following documents:

/geo-examples/Hwy-101.xml
/geo-examples/Holly-St.xml
/geo-examples/Wildlife-Refuge.xml
/geo-examples/Shopping-Center.xml
/geo-examples/MarkLogic-Neighborhood.xml
/geo-examples/Airport.xml

The following is the equivalent structured query, expressed as JSON.

{"query": {
  "geo-region-path-query": {
    "path-index": { "text": "/envelope/cts-region" },
    "coord": "wgs84",
    "geospatial-operator": "intersects",
    "polygon": [
      { "point": [
        { "latitude": 37.519087, "longitude": -122.26346 },
        { "latitude": 37.521299, "longitude": -122.24805 },
        { "latitude": 37.512279, "longitude": -122.24462 },
        { "latitude": 37.50336,  "longitude": -122.24556 },
        { "latitude": 37.506185, "longitude": -122.25981 },
        { "latitude": 37.513436, "longitude": -122.26337 },
        { "latitude": 37.519087, "longitude": -122.26346 }
      ] }
    ]
  }
}}

You can use this query with a curl command similar to the XML example. Just change the request body content type and, potentially, name of the file containing the body. For example:

curl --anyauth --user user:password -X POST -i \
  -d @./body.json -H "Content-type: application/json" \
  'http://localhost:8000/v1/search'

For more details, see Using and Configuring Query Features in the REST Application Developer's Guide and Searching Using Structured Queries.

Example: Using the Envelope Pattern to Encode Regions

Content you search with a region query must be in WKT or serialized cts:region format. This example illustrates using the 'envelope pattern' to encapsulate the searchable region format with original data in an incompatible format. This is not the only solution to this problem. For example, you can tranform your content before ingesting it into MarkLogic, or you can replace the unsupported original format entirely, rather than persisting both.

The example reads in a file from the file system that contains an aggregate XML element contains a several KML Placemark elements. The data is disaggregated into one file per Placemark, and then each Placmark is wrapped in an 'envelope' that contains both the original data and the serialized representation of a cts:region that corresponds to the region in the original data.

For example, if the original input file has the following structure:

<kml xmlns="http://www.opengis.net/kml/2.2">
  <Placemark>
    <name>Hwy 101</name>
    <description>...</description>
    <LineString>
      <extrude>0</extrude>
      <tessellate>1</tessellate>
      <altitudeMode>clampedToGround</altitudeMode>
      <coordinates>
        -122.2637558,37.5206187 -122.2428131,37.5020318
      </coordinates>
    </LineString>
  </Placemark>
  <Placemark>...</Placemark>
  ...
</kml>

Then the result is one document per Placemark, with the following structure. The envelope root element and the cts-region element are created by the ingest transformation.

<envelope>
  <cts-region>LINESTRING(-122.26376 37.520619,-122.24281 37.502032)</cts-region>
  <Placemark xmlns="http://www.opengis.net/kml/2.2">
    <name>Hwy 101</name>
    <description>...</description>
    <LineString>
      <extrude>0</extrude>
      <tessellate>1</tessellate>
      <altitudeMode>clampedToGround</altitudeMode>
      <coordinates>
        -122.2637558,37.5206187 -122.2428131,37.5020318
      </coordinates>
    </LineString>
  </Placemark>
<envelope>

The following example query ingests the original data and creates a document from the envelope it wraps around each Placemark:

xquery version "1.0-ml";
import module namespace geokml = "http://marklogic.com/geospatial/kml"
         at "/MarkLogic/geospatial/kml.xqy";
declare namespace kml="http://www.opengis.net/kml/2.2";

(: Convert the KML regions into cts regions :)
declare function local:region-convert(
  $nodes as node()*
) as cts:region*
{
  for $n in $nodes return
  typeswitch($n)
    case element(kml:Polygon) return geokml:parse-kml($n)
    case element(kml:LineString) return geokml:parse-kml($n)
    case element(kml:Point) return ()
    default return local:region-convert($n/node())
};

(: Create a doc for each KML Placemark, with a wrapper around
 : the KML that contains the cts region equiv of the KML region :)
let $file := xdmp:document-get("/space/geo/ml2.xml")
return
  for $place in $file//*:Placemark
  let $basename := fn:string-join(fn:tokenize($place/*:name, " "),"-")
  return xdmp:document-insert(
    fn:concat("/example/",$basename,".xml"),
    <envelope>{
     let $converted-region := local:region-convert($place)
     return if (fn:empty($converted-region)) 
       then ()
       else <cts-region>{$converted-region}</cts-region>
    }
    {$place}
    </envelope>,
    xdmp:default-permissions(), "geo-example")

Points are not translated by the above example simply because it was not necessary for this purpose. You can index and use point queries on in KML points without transformation. You only need to transform them if you want to use a region query on point data.

Controlling Coordinate System and Precision

The Relationship Between Precision and Coordinate System

The coordinate system and precision are conflated in the coordinate system name in many operations that accept a coordinate system name as input.

For example, when you specify 'wgs84' as the value of the 'coordinate-system' option in a query constructor, it also implicitly specifies single precision. Similarly, a value of 'wgs84/double' specifies both the WGS84 coordinate system and double precision.

In many interfaces, you can use a precision option or parameter to override the precision implied by the coordinate system name.

Determining the Best Precision for Your Application

MarkLogic always preserves the precision of geospatial data in your documents. For example, if you ingest documents containing double precision coordinate values, those values retain full precision, even if the governing coordinate system during ingestion specifies single precision.

However, the precision of values in a geospatial index is determined by the configuration of the index. Thus, you might have double precision values in your documents, but single precision values in the corresponding geospatial index.

A double precision index enables a greater degree of accuracy when computing geospatial search matches, but at the cost of increased memory requirements and some computational overhead.

Greater precision does not equate to greater accuracy. Most applications do not require double precision indexing.

For example, geospatial queries against single precision indexes are accurate to within 1 meter for geodetic coordinate systems. If your application does not require sub-meter accuracy, then there is no reason to incur the overhead of a double precision index.

The following are examples of geospatial applications that might require double precision:

  • Tracking equipment moving around a facility.
  • Tracking room-to-room movements within a building.
  • Tracking slow-moving objects that move in sub-meter increments, such as fault lines and tectonic plates.
  • Tracking assets that require high placement precision, such on which side of a street a fire hydrant is located.

Excessive precision can cause difficulty for geospatial operations. For example, when comparing two points at double precision, they will fail a test for equality if the coordinate values differ when compared at the level of microns. Most applications would consider such a difference 'in the noise' and consider these points the same. Comparison of double-precision coordinates assumes a tolerance of zero by default, meaning they must match exactly, at all digits of precision. This affects operations such as comparison of points, testing whether a point is on a edge, and testing two edges for adjacency. You can use the tolerance option available on some operations to enable less precise comparisons. For more details, see Understanding Tolerance.

An application can use a mix of single and double precision geospatial indexes and operations. For example, you can define both a single and a double precision index over the same data. You can specify precision per operation.

You can control geospatial precision in the following ways:

You can specify precision in conjunction with the coordinate system name in most MarkLogic geospatial interfaces. For example, the 'wgs84' and 'raw' coordinate system names imply single precision, while the 'wgs84/double' and 'raw/double' coordinate system names specify double precision.

How MarkLogic Selects the Governing Coordinate System

When MarkLogic evaluates your XQuery or Server-Side JavaScript code, the governing coordinate system is the first of the following settings found. For more details, see The Governing Coordinate System.

  • Per-operation coordinate system option or parameter
  • Per-module coordinate system, as specified by the XQuery xdmp:coordinate-system prolog option. (This feature is only available in XQuery main modules.)
  • App Server default coordinate system

If you specify a precision using the precision option of an operation, the specified precision always takes precedence over the precision implied by the governing coordinate system name.

The following examples illustrate the governing coordinate system applied in several calling contexts if the App Server default coordinate system is 'wgs84'.

LanguageExample

XQuery

No prolog option

(: app server default is wgs84 :)
xquery version "1.0-ml";
<wrapper>
  <wgs84>{   (: app server default coord-sys :)
    geo:distance(cts:point(1.0,1.0), cts:point(2.0,2.0))
  }</wgs84>
  <wgs84d>{  (: per-operation coord-sys :)
    geo:distance(cts:point(1.0,1.0), cts:point(2.0,2.0), 
                 ("coordinate-system=wgs84/double"))
  }</wgs84d>
  <raw>{     (: per-op coord-sys, with precision override :) 
    geo:distance(cts:point(1.0,1.0), cts:point(2.0,2.0),
                 ("coordinate-system=raw/double",
                  "precision=float"))
  }</raw>
</wrapper>

(: produces:
  <wrapper>
    <wgs84>97.4783192326097</wgs84>
    <wgs84-d>97.4783199874495</wgs84-d>
    <raw>1.4142135623731</raw>
  </wrapper>
:)

XQuery

Prolog option

(: app server default is wgs84 :)
xquery version "1.0-ml";
declare option xdmp:coordinate-system "raw";
<wrapper>
  <raw>{     (: per module coord-sys :)
    geo:distance(cts:point(1.0,1.0), cts:point(2.0,2.0))
  }</raw>
  <wgs84d>{  (: per-operation coord-sys :)
    geo:distance(cts:point(1.0,1.0), cts:point(2.0,2.0), 
                 ("coordinate-system=wgs84/double"))
  }</wgs84d>
  <wgs84>{   (: per-op coord-sys, with precision override :) 
    geo:distance(cts:point(1.0,1.0), cts:point(2.0,2.0),
                 ("coordinate-system=wgs84/double",
                  "precision=float"))
  }</wgs84>
</wrapper>

(: produces:
  <wrapper>
    <raw>1.4142135623731</raw>
    <wgs84d>97.4783199874495</wgs84d>
    <wgs84>97.4783192326097</wgs84>
  </wrapper>
:)
Server-Side JavaScript
const result = {
  wgs84:    // app server default coord-sys
    geo.distance(cts.point(1.0,1.0), cts.point(2.0,2.0)),
  wgs84d:   // op specific coord-sys
    geo.distance(cts.point(1.0,1.0), cts.point(2.0,2.0), 
                 ['coordinate-system=wgs84/double']),
  raw:      // op specific coord-sys w precision override
    geo.distance(cts.point(1.0,1.0), cts.point(2.0,2.0), 
                 ['coordinate-system=raw/double',
                  'precision=float'])
};
result

/* produces:
  { "wgs84":97.4783192326097, 
    "wgs84d":97.4783199874495, 
    "raw":1.4142135623731
  }
 */

See the following topics for instructions on setting the coordinate system and precision at various levels:

Probing the Governing Coordinate System Name

You can probe the governing coordinate system using the XQuery function geo:default-coordinate-system or the JavaScript function geo.defaultCoordinateSystem. (This function can only account for the App Server default and per-module settings.)

The following examples illustrate how to retrieve the name of the governing coordinate system.

LanguageExample
XQuery
xquery version "1.0-ml";
geo:default-coordinate-system();
(: returns the app server default coordinate system :)

xquery version "1.0-ml";
declare option xdmp:coordinate-system "wgs84/double";
geo:default-coordinate-system();
(: returns the per-module setting, wgs84/double :)
Server-Side JavaScript
geo.defaultCoordinateSystem();

// returns the app server default coordinate system

Specifying the App Server Default Coordinate System

You can use the following Admin library functions to set and get the default coordinate system/precision combination for an App Server. If you do not explicitly set the coordinate system and precision, it is 'wgs84' (single precision).

For example, the following XQuery code sets the default coordinate system for the App Server named 'MyAppServer' to 'wgs84/double'.

xquery version "1.0-ml";
import module namespace admin = "http://marklogic.com/xdmp/admin" 
  at "/MarkLogic/admin.xqy";

let $config := admin:get-configuration()
let $groupid := admin:group-get-id($config, "Default")
return admin:save-configuration(
  admin:appserver-set-coordinate-system(
    $config, 
    admin:appserver-get-id($config, $groupid, "MyAppServer"), 
    "wgs84/double")
)

You can also use the XQuery Admin library module from Server-Side JavaScript. The following example is equivalent to the previous XQuery code.

const admin = require('/MarkLogic/admin');
const config = admin.getConfiguration();
const groupId = admin.groupGetId(config, 'Default');
admin.saveConfiguration(
  admin.appserverSetCoordinateSystem(
    config, 
    admin.appserverGetId(config, groupId, 'MyAppServer'), 
    'wgs84/double')
)

To determine the canonical name for a coordinate system/precision combination, use the XQuery function geo:coordinate-system-canonical or the JavaScript function geo.coordinateSystemCanonical.

For more details, see the XQuery and XSLT Reference Guide.

Specifying the Per-Module Coordinate System

In XQuery, you can use the xdmp:coordinate-system prolog option to override the App Server default coordinate system module-wide. This option is only available in XQuery. For example:

declare option xdmp:coordinate-system "wgs84/double";

The override only takes effect when you declare the option in an XQuery main module, but it affects any library module functions subsequently invoked from that main module.

REST, Java, and Node.js Client API resource extensions are library modules, so you cannot override the coordinate system in your extension implementation. Use the ad-hoc query (eval or invoke) of the Client APIs or a per-operation override if you need to override the App Server default with these APIs.

For example, if you create a library function that just returns the result of calling geo:default-coordinate-system, then the following main module will return 'wgs84/double' for the coordinate system.

xquery version "1.0-ml";
import module namespace my = "http://marklogic.com/example/my-lib"
  at "/my/lib.xqy";

declare option xdmp:coordinate-system "wgs84/double";
my:get-coord-sys()

Specifying a Per-Operation Coordinate System and Precision

Many geospatial operations accept options for specifying the coordinate system and/or precision. A per-operation specification overrides the governing coordinate system. For example:

LanguageExample
XQuery
(: xquery :)
geo:distance(
  cts:point(1.0,1.0), cts:point(2.0,2.0), 
  ("coordinate-system=wgs84", "precision=double"))
Server-Side JavaScript
(: javascript :)
geo.distance(
  cts.point(1.0,1.0), cts.point(2.0,2.0), 
  ['coordinate-system=wgs84', 'precision=double'])

Where both the coordinate-system and precision options are supported, you can specify the precision either as part of the coordinate system canonical name or independently. Where there is a conflict between the precision in the coordinate system name and the precision option, the precision option takes precedence.

The following example illustrates how the option settings interact:

OptionsResulting Coordinate System
coordinate-system=wgs84/double
wgs84/double
coordinate-system=wgs84
precision=double
wgs84/double
coordinate-system=wgs84/double
precision=float
wgs84 (single precision)

You can get the canonical name for a coordinate system/precision combination using the XQuery function geo:coordinate-system-canonical or the JavaScript function geo.coordinateSystemCanonical.

Specifying Coordinate System During Index Creation

You can choose single or double precision when creating a geospatial index. This determines the precision of the values stored in the index. For example, you can create a single precision index over your geospatial data even if the data in your documents is double precision.

Specify precision during index creation through the coordinate system name. You can use the XQuery function geo:coordinate-system-canonical or the JavaScript function geo.coordinateSystemCanonical to generate the canonical name of the desired coordinate system and precision combination.

For example, the following code creates a geospatial element index for double precision wgs84 point values:

LanguageExample
XQuery
xquery version "1.0-ml";
import module namespace admin = "http://marklogic.com/xdmp/admin" 
  at "/MarkLogic/admin.xqy";

let $config := admin:get-configuration()
let $dbid := xdmp:database("Documents")
let $geo-index-spec := admin:database-geospatial-element-index(
  "/my/namespace", "elementname", 
  geo:coordinate-system-canonical("wgs84", "double"), 
  fn:false() )
return admin:save-configuration(
  admin:database-add-geospatial-element-index(
    $config, $dbid, $geo-index-spec)
)
Server-Side JavaScript
const admin = require('/MarkLogic/admin');

const config = admin.getConfiguration()
const dbid = xdmp.database('Documents')
const geoIndexSpec = admin.databaseGeospatialElementIndex(
  '/my/namespace', 'elementName', 
  geo.coordinateSystemCanonical('wgs84', 'double'), 
  false)
admin.saveConfiguration(
  admin.databaseAddGeospatialElementIndex(config, dbid, geoIndexSpec)
)

For more details on geospatial indexes, see Understanding Geospatial Query and Index Types.

Understanding Tolerance

Tolerance is the largest allowable variation in geometry calculations. Tolerance is a distance within which two points are considered equal, a point is consider 'on' an edge, or two edges are considered touching. Many geospatial functions in MarkLogic accept a 'tolerance' option.

See the following topics for more details:

How Tolerance Affects Geometric Comparisons

Tolerance defines the 'largest acceptable error' when comparing two points for equality.

For example, a tolerance of zero means two points only match if they're exactly the same, out to the least significant digit. Thus, two points separated by a distance measurable in microns would not match. If you're trying to determine whether a truck is parked at the door of a building, such a high degree of precision is a hindrance. Use tolerance to filter out differences that are 'in the noise'.

The following diagram illustrates how tolerance affects point comparison. A, B, and C are points. The shaded circle describes the space within which points are considered 'equal to' A, based on the tolerance. Point B falls within tolerance of A, so A and B are considered equal. Point C is further from A than the tolerance allows, so A and C are not considered equal.

When comparing edges, a point is considered as lying 'on the edge' if the distance from the point to the edge is within the tolerance. For example, in the following diagram, any point in the green circles coincides with an endpoint of the edge. Any point within the pink region lies on the edge.

Operations such computing whether two polygons intersect require comparing two edges. Two edges overlap if both endpoints of one edge lie on the other, or if an endpoint of each edge lies on the other.

For example, the following diagram illustrates the effect of two different tolerance values on determining overlap. The green circles represent the tolerance of each endpoint. With the smaller tolerance, the edges do not overlap. With the larger tolerance, both endpoints of one edge are within tolerance of the other edge, so the edges overlap.

Considerations for Tolerance Selection

If you do not explicitly set tolerance, MarkLogic uses the default tolerance appropriate for the coordinate system.

To ensure accurancy, MarkLogic enforces a minimum tolerance for each coordinate system. If tolerance is too precise, then the calculation of distance might not be accurate to the specified level of precision.

You cannot choose a tolerance value less than zero.

For most operations, MarkLogic interprets a tolerance of zero as the minimum tolerance for the coordinate system. The only exceptions are the XQuery function geo:bounding-boxes and the JavaScript function geo.boundingBoxes are the exception, as follows:

When computing bounding boxes, a non-zero tolerance causes the bounding boxes to be 'padded' by the tolerance amount. This ensures the bounding box covers the 'thickened' boundary of the region under consideration. If you set tolerance to zero when computing bounding boxes, then the bounding boxes are not padded at all.

When considering a polygon, tolerance effectively 'thickens' the boundary of the polygon. If you set the tolerance too high relative to the size of the polygon, the polygon degenerates. This can result in unexpected results or errors.

You can use the XQuery geo:region-approximate or the Server-Side JavaScript function geo.regionApproximate to simply your region(s) before performing geometric computations. The simplification can sometimes help you balance tolerance against polygon degneration.

Geospatial computational and comparison operations that do not accept a tolerance option behave as if tolerance is set to zero.

Summary of Other Geospatial Operations

The following APIs are used to perform various operations and calculations on geospatial data:

XQueryJavaScript
geo:polygon-containsgeo.polygonContains
geo:complex-polygon-containsgeo.complexPolygonContains
geo:region-contains geo.regionContains
geo:arc-intersectiongeo.arcIntersection
geo:box-intersectsgeo.boxIntersects
geo:circle-intersectsgeo.circleIntersects
geo:polygon-intersectsgeo.polygonIntersects
geo:complex-polygon-intersectsgeo.complexPolygonIntersects
geo:region-intersectsgeo.regionIntersects
geo:region-approximate geo.regionApproximate
geo:region-clean geo.regionClean
geo:bounding-boxes geo.boundingBoxes
geo:polygon-to-linestring geo.polygonToLinestring
geo:linestring-reverse geo.linestringReverse
geo:linestring-concat geo.linestringConcat
geo:circle-polygon geo.circlePolygon
geo:ellipse-polygon geo.ellipsePolygon
geo:interior-point geo.interiorPoint
geo:count-vertices geo.countVertices
geo:count-distinct-vertices geo.countDistinctVertices
geo:remove-duplicate-vertices geo.removeDuplicateVertices
geo:distancegeo.distance
geo:shortest-distancegeo.shortestDistance
geo:destinationgeo.destination
geo:bearinggeo.bearing
geo:approx-centergeo.approxCenter
geo:region-affine-transform geo.regionAffineTransform
geo:region-relate geo.regionRelate
geo:region-de9im geo.regionDe9im

For signatures and more details, see the Library Module section of the MarkLogic XQuery and XSLT Function Reference or the MarkLogic Server-Side JavaScript Function Reference.

Converting To and From Common Geospatial Representations

MarkLogic provides interfaces for converting between MarkLogic geospatial primitive types and several common geospatial text, XML, and JSON representations. This section covers the following topics:

Conversion Overview

You can use MarkLogic APIs to convert to and from the following common geospatial representations:

  • Well-Known Text (WKT)
  • Well-Known Binary (WKB)
  • GML
  • KML
  • GeoJSON
  • GeoRSS

For example, the following XQuery code uses geogml:parse-gml to convert a GML region into a cts:region (a polygon in this case). This function determines the output cts:region type from the kind of input GML region.

import module namespace geogml ="http://marklogic.com/geospatial/gml"
  at "/MarkLogic/geospatial/gml.xqy";

geogml:parse-gml(
  <gml:Polygon srsName="ML:wgs84"
      xmlns:gml="http://www.opengis.net/gml/3.2">
    <gml:exterior>
      <gml:LinearRing>
        <gml:posList srsDimension="2">
          5.0 1.0 8.0 1.0 8.0 6.0 5.0 7.0 5.0 1.0
        </gml:posList>
      </gml:LinearRing>
    </gml:exterior>
  </gml:Polygon>
)

If you know the input region type, you can also use one of the region-specific constructors to perform the equivalent conversion. For example, the above code could use geogml:polygon instead of geogml:parse-gml. For details, see Constructing Geospatial Point and Region Values.

The following Server-Side JavaScript code converts a GeoJSON polygon into a cts.polygon:

// Create a cts.polygon from a GeoJSON polygon
const geojson = require('/MarkLogic/geospatial/geojson.xqy');

geojson.polygon(
  { type: 'Polygon', 
    coordinates: [
      [[100.0,0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0]]
  ] }
)

For each format, the XQuery API includes a parse-format function for converting from the common representation to a MarkLogic geospatial primitive type, and the JavaScript API includes a parseFormat function for the same purpose. This operation is equivalent to calling the geo:parse XQuery function or the geo.parse JavaScript function with input of the same format. The API also includes a to-format XQuery function and toFormat JavaScript function for converting from a MarkLogic primitive type to the target format.

For example, the GeoJSON library module includes the following functions that can be used to convert data between GeoJSON and cts:region.

XQueryJavaScript
geojson:parse-geojsongeojson.parseGeojson
geojson:to-geojsongeojson.toGeojson
geojson:boxgeojson.box
geojson:circlegeojson.circle
geojson:complex-polygongeojson.complexPolygon
geojson:linestringgeojson.linestring
geojson:multi-linestringgeojson.multiLinestring
geojson:pointgeojson.point
geojson:polygongeojson.polygon

You can use the built-in geo:parse XQuery function or geo.parse JavaScript function to convert nodes in any of the supported formats into an equivalent MarkLogic geospatial primitive type, without regard to the input format or region type. For best performance, if you know the format, use the equivalent format-specific functions.

WKT and WKB Conversions in XQuery

MarkLogic represents geospatial data using the cts:region type and types derived from it, such as cts:point, cts:polygon, and cts:circle. You can convert from WKT or WKB into cts:region items and from cts:region into WKT or WKB.

Use the geo:parse-wkt function to convert WKT data into a sequence of cts:region items. Similarly, use geo:parse-wkb to convert WKB data into a sequence of cts:region items. You can use the resulting items in geospatial cts:query constructors or geospatial operations.

For example, the following call converts a WKT polygon with an inner and outer boundary into a cts:complex-polygon:

geo:parse-wkt("
 POLYGON(
  (0 0, 0 10, 10 10, 10 0, 0 0),
  (0 5, 0 7, 5 7, 5 5, 0 5) )" )

The input to geo:parse-wkb is a binary node that contains a WKB byte sequence. For example, the following code converts a WKB byte sequence representing the coordinates (-73.700380647, 40.739754168) into a cts:point:

geo:parse-wkb(
  binary { "010100000072675909D36C52C0E151BB43B05E4440" }
)

To convert from cts:region to WKT, use geo:to-wkt. For example, the following code returns a WKT POINT:

geo:to-wkt(cts:point(1, 2))

Similarly, the following code returns a WKB POINT:

geo:to-wkb(cts:point(1, 2))

You cannot convert a cts:circle or a cts:box to WKT. For more details on WKT, see http://en.wikipedia.org/wiki/Well-known_text.

WKT and WKB Conversions in JavaScript

MarkLogic represents geospatial data using the cts.region type and types derived from it, such as cts.point, cts.polygon, and cts.circle. MarkLogic provides the following conversions between WKT or WKB and cts.region: You can use the cts.region representation cts:query constructors or geospatial operations.

  • Explict conversion from WKT or WKB to cts.region using the geo.parseWkt function. For example:
    // Convert WKT polygon into a cts.complexPolygon
    geo.parseWkt(
      'POLYGON((0 0, 0 10, 10 10, 10 0, 0 0),(0 5, 0 7, 5 7, 5 5, 0 5))'
    )
    
    // Convert WKB bye sequence representing the coordinates
    // (-73.700380647, 40.739754168) into a cts.point
    geo.parseWkb(
      new NodeBuilder()
        .addBinary('010100000072675909D36C52C0E151BB43B05E4440')
        .toNode()
    )
  • Explicit converstion from cts.region to WKT or WKB using the geo.toWkt or geo.toWkb functions. For example:
    // cts.point to WKT
    geo.toWkt(cts.point(1, 2))
    
    // cts.point to WKB
    geo.toWkb(cts.point(1, 2))
  • Implicit conversion from WKT to cts.region where the expected type is a cts.region. For example:
    // create a cts.polygon from WKT via implicit conversion
    cts.polygon('POLYGON ((30 10, 40 40, 20 40, 10 20, 30 10))')

Note that geo.parseWkt and geo.toWkt return a Sequence rather than an array or a single value. The input to geo.parseWkb is a binary node that contains a WKB byte sequence.

The supported conversions from WKT to cts.region mean all the following calls pass the same cts.polygon value to geo.polygonContains, which returns true.:

// Use a cts.polygon created from a set of cts.point values
geo.polygonContains(
  cts.polygon([
    cts.point(30,10), cts.point(40,40), cts.point(20,40),
    cts.point(10,30), cts.point(30,10)]),
  cts.point(25,25))

// Use a cts.polygon created by explicitly converting from WKT
geo.polygonContains(
  geo.parseWkt('POLYGON ((30 10, 40 40, 20 40, 10 20, 30 10))'),
  cts.point(25,25))

// Use a cts.polygon created by implicitly converting from WKT
geo.polygonContains(
  cts.polygon('POLYGON ((30 10, 40 40, 20 40, 10 20, 30 10))'),
  cts.point(25,25))

You cannot convert a cts.circle or a cts.box to WKT. For more details on WKT, see http://en.wikipedia.org/wiki/Well-known_text.

Mapping of WKT and WKB Types to MarkLogic Types

The following table shows how the WKT and WKB types map to the MarkLogic geospatial types. That is, the equivalent value type resulting from calling the geo:parse-wkt XQuery function or geo.parseWkt JavaScript function, or the WKB equivalents.

WKT/WKB GeometryMarkLogic XQuery TypeMarkLogic JavaScript Type
POINTcts:pointcts.point
POINT EMPTY (WKT only)cts:point(flagged as empty)cts.point(flagged as empty)
POLYGONcts:complex-polygon| cts:polygoncts.complexPolygon| cts.polygon
POLYGON EMPTYcts:complex-polygon(flagged as empty)cts.complexPolygon(flagged as empty)
LINESTRINGcts:linestringcts.linestring
LINESTRING EMPTYcts:linestring(flagged as empty)cts.linestring(flagged as empty)
TRIANGLEcts:polygoncts.polygon
TRIANGLE EMPTYcts:complex-polygon(flagged as empty)cts.complexPolygon(flagged as empty)
MULTIPOINTcts:point*zero or more cts.point nodes
MULTIPOINT EMPTY()an empty Sequence
MULTILINESTRINGcts:linestring*zero or more cts.linestring
MULTILINESTRING EMPTY()null, empty array, or empty Sequence
MULTIPOLYGON(cts:polygon| cts:complex-polygon)*(cts.polygon| cts.complexPolygon)*
MULTIPOLYGON EMPTY()null, empty array, or empty Sequence
GEOMETRYCOLLECTIONcts:region*zero or more cts.region nodes
GEOMETRYCOLLECTION EMPTY()null, empty array, or empty Sequence
othersthrows XDMP-BADWKTthrows XDMP-BADWKT

Constructing Geospatial Point and Region Values

Use the following APIs to construct geospatial regions. You can use the resulting region values in geospatial query constructors and other geospatial operations, such as those listed in Summary of Other Geospatial Operations.

XQueryJavaScript
cts:boxcts.box
cts:circlects.circle
cts:complex-polygoncts.complexPolygon
cts:linestringcts.linestring
cts:pointcts.point
cts:polygoncts.polygon

These constructors accept either the raw data, such as a pair of float values for constructing a point, or a string representing the serialization of the underlying primitive type. The serialized representation can be either the MarkLogic internal representation, such as a serilaized cts:point, or a WKT serialization. If the primitive is not constructible from the string input, an exception is thrown.

Each constructor produces a region value of the corresponding primitive type. For example, the cts:box constructor function creates a value of type cts:box. Each of the geospatial primitive types is an instance of the cts:region base type (cts.region in JavaScript).

For example, the following call constructs a cts:polygon from a string that is a serialized cts:point value (space-separated points):

XQueryJavaScript
cts:polygon("38,-10 40,-10 39, -15")
cts.polygon('38,-10 40,-10 39, -15')

You can also construct the primitive types from XML or JSON nodes that contain geospatial data in the supported formats. For example, the following XQuery code uses the geokml:box function to construct a cts:box from an XML element containing a KML LatLongBox.

xquery version "1.0-ml";
import module namespace geokml = "http://marklogic.com/geospatial/kml"
  at "/MarkLogic/geospatial/kml.xqy";

geokml:box(
  <LatLongBox xmlns="http://www.opengis.net/kml/2.2">
    <north>30</north>
    <south>12.5</south>
    <east>-122.24</east>
    <west>-127.24</west>
  </LatLongBox>)

Similarly, the following example uses geojson.box JavaScript function to construct a cts.box from a JSON node that contains a suitable GeoJSON polygon. For example:

const geojson = require('/MarkLogic/geospatial/geojson.xqy');

geojson.box(
  { type: 'Feature',
    bbox: [-180.0, -90.0, 180.0, 90.0],
    geometry: {
      type: 'Polygon',
      coordinates: [[
        [-180.0, 10.0], [20.0, 90.0], [180.0, -5.0], [-30.0, -90.0]
      ]]
  }}
)

For details and examples on these functions, see the MarkLogic XQuery and XSLT Function Reference or the MarkLogic Server-Side JavaScript Function Reference.

Geospatial Query Support in Other APIs

The Search API enables geospatial queries through the following features:

For information on specific geospatial query options, see Appendix: Query Options Reference.

The Client APIs for REST, Java and Node.js applications provide similar support. For more details and example see the following topics:

Preparing to Run the Examples

Use the instructions in this section to load the data and configure the indexes used in several examples in this chapter. The following topics are covered:

Overview of the Sample Data

The sample data contains points, linestrings, and polygons associated with landmarks near the MarkLogic headquarters. The following diagram approximates the relative positions of the features in the sample data.

This geospatial data is made available in two formats: KML (XML) and GeoJSON. Each document describes a single point or a region. The points and regions are the same in the two types of documents (XML and JSON). For example, the documents '/geo-examples/Airport.xml' and '/geo-examples/Airport.json' describe the same region.

The documents are added to the following collections to make it easy to select the type of data to work with just XML, just JSON, or both formats.

Document SetCollections
KMLgeo-examples, geo-xml-examples
GeoJSONgeo-examples, geo-json-examples

Each document uses the envelope pattern to encapsulate the original KML or GeoJSON region coordinates with a serialized cts region that is suitable for use with cts:geospatial-region-query. The unprocessed KML input is a single XML file that contains a series of KML Placemark elements. The following data snippet shows the structure of the raw input:

<kml xmlns="http://www.opengis.net/kml/2.2">
<Folder>
  <Placemark>
    <name>Hwy 101</name>
    <LineString>
      <extrude>0</extrude>
      <tessellate>1</tessellate>
      <coordinates>
        -122.2637558,37.5206187 -122.2428131,37.5020318
      </coordinates>
    </LineString>
  </Placemark>
  <Placemark>...</Placemark>
  ...
</Folder>
</kml>

The ingestion process splits the input into one document per Placemark. The envelope pattern is used to encapsulate the original Placemark with an equivalent serialized cts:region if the Placemark represents a non-point region. Region queries only operate on regions expressed as WKT or serialized cts:regions, so you cannot query the Placemark coordinates directly. (Point regions are left untranslated for convenience in demonstrating point queries; you could choose to treat them the same way.)

The following examples shows the final document format, with the envelope root element and the cts-region element created by the ingest transformation. If a Placemark represents a point, then no cts-region element is added because it is not needed.

<envelope>
  <cts-region>LINESTRING(-122.26376 37.520619,-122.24281 37.502032)</cts-region>
  <Placemark xmlns="http://www.opengis.net/kml/2.2">
    <name>Hwy 101</name>
    <LineString>
      <extrude>0</extrude>
      <tessellate>1</tessellate>
      <coordinates>
        -122.2637558,37.5206187 -122.2428131,37.5020318
      </coordinates>
    </LineString>
  </Placemark>
<envelope>

The GeoJSON data receives similar treatment. The raw input is a feature collection. Ingestion creates one document per feature. The envelope pattern is used to encapsulate each feature with an equivalent serialized cts:region to facilitate queries. Point regions are not transformed.

For example, the raw GeoJSON input has the following structure:

{ "type": "FeatureCollection",
  "features": [
    { "type": "Feature",
      "geometry": {
        "type": "Point", 
        "coordinates": [-122.2465038,37.5073428]},
      "properties": {"name": "MarkLogic HQ"}
    },
    { "type": "Feature", ...},
    ...
  ]
} 

Ingestion produces documents of the following form. For documents containing a region, the ingestion transformation adds the envelope wrapper and a ctsRegion. For documents containing a point, the ingestion transformation just adds the envelope wrapper.

{ "envelope": {
  "feature": {
    "type": "Feature", 
    "geometry": {
      "type": "LineString", 
      "coordinates": [...]
    }
    "properties": { "name": "Holly St" } }, 
  "ctsRegion": "LINESTRING(...))"
} }

You can thus use region queries on /envelope/cts-region (XML) or /envelope/ctsRegion (JSON) and point queries on /envelope/kml:Placemark/Point/coordinates (XML) or //geometry[type = 'Point']/array-node('coordinates') (JSON).

Configuring the Indexes

This section walks through configuring a point index and a region index over the XML and JSON samples documents, for a total of 4 indexes. Separate indexes are used for each data set to showcase a variety of indexes and to make it easy to focus on one content type or the other.

Point indexes are optional for some types of geospatial point queries, but required for geospatial point range queries and lexicon operations. An index is usually recommended for best performance.

Geospatial region queries always required an index.

You can skip over indexes related to content that does not interest you. For example, you can skip the XML-related indexes if you are only interested in JSON. However, some examples in this chapter may not work properly without the related indexes.

Choose one of the following methods to create the indexes.

You can also create indexes using the REST Management API. This method is not included here. For details, see the MarkLogic REST API Reference.

Creating Indexes Using the Admin Interface

The table below summarizes the configuration characteristics of the indexes you should create in the Admin Interface. Use this information in Steps 5 and 7 of the procedure below. Use the default value for any characteristic not specified here.

Index TypeCharacteristics
Geospatial Element Child Index (Point, XML)

Parent namespace URI: http://www.opengis.net/kml/2.2

Parent localname: Point

Childe namespace URI: http://www.opengis.net/kml/2.2

Child localname: coordinates

Coordinate system: wgs84

Point format: long-lat-point

Geospatial Path Index (Point, JSON)

Path expression: //geometry[type = 'Point']/array-node('coordinates')

Coordinate system: wgs84

Point format: long-lat-point

Geospatial Region Index (XML)

Path expression: /envelope/cts-region

Coordinate system: wgs84

Geohash precision: 2

Geospatial Region Index (JSON)

Path expression: /envelope/cts-region

Coordinate system: wgs84

Geohash precision: 2

Use the following procedure to create the geospatial indexes using the above configuration information. For more information about the Admin Interface, see Administrator's Guide.

  1. Navigate to the Admin Interface in your browser. For example, navigate to http://localhost:8001 if your MarkLogic installation is on localhost. Authenticate as a user with administrative privileges.
  2. Click Databases in the tree menu on the left to expand the list of databases. The tree menu expands to display the available databases.
  3. Click the name of the database for which you want to create an index. For example, click Documents. The tree menu expands to display the configuration categories for this database.
  4. Click Geospatial Indexes icon in the tree menu, under the selected database.
  5. To create a point index:
    1. Click Geospatial Point Indexes in the tree menu, under the selected database.
    2. Click the type of point index you want to create. For example, click Geospatial Element Child Indexes. The configuration page for this index type is displayed on the right.
  6. To create a region index, click Geospatial Region Indexes in the tree menu, under the selected database. The configuration page for this index type is displayed on the right.
  7. Click the Add tab at the top of the configuration page.
  8. Fill in the configuration from the data in the table above.
  9. Click OK to create the index.
  10. Repeat from Step 5 until you have created all the required indexes.
Creating the Indexes with XQuery

Use the procedure in this section to create the indexes using XQuery and Query Console. If you are not familiar with Query Console, see the Query Console User Guide. For equivalent JavaScript instructions, see Creating the Indexes with JavaScript.

The following procedure creates two point indexes and two region indexes.

  1. Navigate to Query Console in your browser. For example, navigate to http://localhost:8000 if your MarkLogic installation is on localhost.
  2. Copy the following code into a new query tab in Query Console.
    xquery version "1.0-ml";
    import module namespace admin = "http://marklogic.com/xdmp/admin" 
      at "/MarkLogic/admin.xqy";
    
    let $database := "Documents"
    let $config := admin:get-configuration()
    let $config :=         (: point index for XML docs :)
      admin:database-add-geospatial-element-child-index(
        $config, admin:database-get-id($config, $database),
        admin:database-geospatial-element-child-index(
          "http://www.opengis.net/kml/2.2", "Point",
          "http://www.opengis.net/kml/2.2", "coordinates",
          "wgs84", fn:false(), "long-lat-point", "reject")
      )
    let $config :=         (: point index for JSON docs :)
      admin:database-add-geospatial-path-index(
        $config, admin:database-get-id($config, $database),
        admin:database-geospatial-path-index(
          "//geometry[type = 'Point']/array-node('coordinates')", 
          "wgs84", fn:false(), "long-lat-point", "reject")
      )
    let $config :=         (: region index over XML docs :)
      admin:database-add-geospatial-region-path-index(
        $config, admin:database-get-id($config, $database),
        admin:database-geospatial-region-path-index(
          "/envelope/cts-region", "wgs84", 2, "reject")
      )
    let $config :=         (: region index over JSON docs :)
      admin:database-add-geospatial-region-path-index(
        $config, admin:database-get-id($config, $database),
        admin:database-geospatial-region-path-index(
          "/envelope/ctsRegion", "wgs84", 2, "reject")
      )  
    (: create the configured indexes :)
    return admin:save-configuration($config)
  3. If you are not using the Documents database for your content database, modify the value of the $database variable.
  4. Select XQuery in the Query Type dropdown if it is not already selected.
  5. Click the Run button in Query Console to create the indexes.

The script produces no output when it is successful. You can use the Admin Interface to explore the geospatial indexes and confirm the indexes were created.

For the next step, see Creating the Input Data Files.

Creating the Indexes with JavaScript

Use the procedure in this section to create the indexes using Server-Side JavaScript and Query Console. If you are not familiar with Query Console, see the Query Console User Guide. For equivalent XQuery instructions, see Creating the XML Input File.

The following procedure creates two point indexes and two region indexes.

  1. Navigate to Query Console in your browser. For example, navigate to http://localhost:8000 if your MarkLogic installation is on localhost.
  2. Copy the following code into a new query tab in Query Console.
    const admin = require('/MarkLogic/admin');
    
    const database = 'Documents';
    const config = admin.getConfiguration();
    
    // Point index over the XML samples
    config = admin.databaseAddGeospatialElementChildIndex(
        config, admin.databaseGetId(config, database),
        admin.databaseGeospatialElementChildIndex(
          'http://www.opengis.net/kml/2.2', 'Point',
          'http://www.opengis.net/kml/2.2', 'coordinates',
          'wgs84', false, 'long-lat-point', 'reject')
      );
    // Point index over the JSON samples
    config = admin.databaseAddGeospatialPathIndex(
        config, admin.databaseGetId(config, database),
        admin.databaseGeospatialPathIndex(
          '//geometry[type = "Point"]/array-node("coordinates")', 
          'wgs84', false, 'long-lat-point', 'reject')
      );
    // Region index over the XML samples
    config = admin.databaseAddGeospatialRegionPathIndex(
        config, admin.databaseGetId(config, database),
        admin.databaseGeospatialRegionPathIndex(
          '/envelope/cts-region', 'wgs84', 2, 'reject')
      );
    // Region index over the JSON samples
    config = admin.databaseAddGeospatialRegionPathIndex(
        config, admin.databaseGetId(config, database),
        admin.databaseGeospatialRegionPathIndex(
          '/envelope/ctsRegion', 'wgs84', 2, 'reject')
      );
    
    // Create the configured indexes.
    admin.saveConfiguration(config);
  3. If you are not using the Documents database for your content database, modify the value of the database variable.
  4. Select JavaScript in the Query Type dropdown if it is not already selected.
  5. Click the Run button in Query Console to create the indexes.

The script produces no output when it is successful. You can use the Admin Interface to explore the geospatial indexes and confirm the indexes were created.

Creating the Input Data Files

Follow the instructions in this section to create two files containing the raw XML and JSON sample data. These files are used by the procedure in Loading the Sample Data. Create both files, unless you plan to skip the examples involving one document format.

Creating the XML Input File

Copy the following data to a file on the filesystem. Choose a location that is readable by your MarkLogic installation. You can use any file name, but the subsequent instructions assume 'geo-examples.xml'.

Next, create the JSON data file following the instructions in Creating the JSON Input File.

<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://www.opengis.net/kml/2.2">
  <Placemark>
    <name>MarkLogic HQ</name>
    <description></description>
    <Point>
      <altitudeMode>clampedToGround</altitudeMode>
      <coordinates>-122.2465038,37.5073428</coordinates>
    </Point>
  </Placemark>
  <Placemark>
    <name>Restaurant</name>
    <description></description>
    <Point>
      <altitudeMode>clampedToGround</altitudeMode>
      <coordinates>-122.2581983,37.5128407</coordinates>
    </Point>
  </Placemark>
  <Placemark>
    <name>Hiller Aviation Museum</name>
    <description></description>
    <Point>
      <altitudeMode>clampedToGround</altitudeMode>
      <coordinates>-122.2527051,37.5128917</coordinates>
    </Point>
  </Placemark>
  <Placemark>
    <name>Hwy 101</name>
    <description>Length: 2.775 km (1.724 mi)</description>
    <visibility>1</visibility>
    <open>0</open>
    <LineString>
      <extrude>0</extrude>
      <tessellate>1</tessellate>
      <altitudeMode>clampedToGround</altitudeMode>
      <coordinates>
        -122.2637558,37.5206187 -122.2428131,37.5020318
      </coordinates>
    </LineString>
  </Placemark>
  <Placemark>
    <name>Holly St</name>
    <description>Length: 1.384 km (0.86 mi)</description>
    <visibility>1</visibility>
    <open>0</open>
    <LineString>
      <extrude>0</extrude>
      <tessellate>1</tessellate>
      <altitudeMode>clampedToGround</altitudeMode>
      <coordinates>
        -122.2598934,37.5096578 -122.2551727,37.5148321
        -122.2536278,37.5172148 -122.2523403,37.5185083
        -122.2520828,37.5202102
      </coordinates>
    </LineString>
  </Placemark>
  <Placemark>
    <name>Wildlife Refuge</name>
    <description>Length: 3.104 km (1.929 mi)</description>
    <visibility>1</visibility>
    <open>0</open>
    <Polygon>
      <extrude>0</extrude>
      <tessellate>1</tessellate>
      <altitudeMode>clampedToGround</altitudeMode>
      <outerBoundaryIs>
        <LinearRing>
          <coordinates>
            -122.2428131,37.5173510 -122.2468472,37.5131641
            -122.2422123,37.5069683 -122.2356892,37.5102365
            -122.2384787,37.5154788 -122.2428131,37.5173510
          </coordinates>
        </LinearRing>
      </outerBoundaryIs>
    </Polygon>
  </Placemark>
  <Placemark>
    <name>MarkLogic Neighborhood</name>
    <description>Length: 5.591 km (3.474 mi)</description>
    <visibility>1</visibility>
    <open>0</open>
    <styleUrl>#track</styleUrl>
    <Polygon>
      <extrude>0</extrude>
      <tessellate>1</tessellate>
      <altitudeMode>clampedToGround</altitudeMode>
      <outerBoundaryIs>
        <LinearRing>
          <coordinates>
            -122.2634554,37.5190870 -122.2480488,37.5212994
            -122.2446156,37.5122790 -122.2455597,37.5033596
            -122.2598076,37.5061853 -122.2633696,37.5134364
            -122.2634554,37.5190870
          </coordinates>
        </LinearRing>
      </outerBoundaryIs>
    </Polygon>
  </Placemark>
  <Placemark>
    <name>Shopping Center</name>
    <description>Length: 0.746 km (0.463 mi)</description>
    <visibility>1</visibility>
    <open>0</open>
    <styleUrl>#track</styleUrl>
    <Polygon>
      <extrude>0</extrude>
      <tessellate>1</tessellate>
      <altitudeMode>clampedToGround</altitudeMode>
      <outerBoundaryIs>
        <LinearRing>
          <coordinates>
            -122.2485638,37.5033937 -122.2465038,37.5015552
            -122.2478342,37.5003635 -122.2502375,37.5022020
            -122.2485638,37.5033937
          </coordinates>
        </LinearRing>
      </outerBoundaryIs>
    </Polygon>
  </Placemark>
  <Placemark>
    <name>Airport</name>
    <description>Length: 2.787 km (1.732 mi)</description>
    <visibility>1</visibility>
    <open>0</open>
    <styleUrl>#track</styleUrl>
    <Polygon>
      <extrude>0</extrude>
      <tessellate>1</tessellate>
      <altitudeMode>clampedToGround</altitudeMode>
      <outerBoundaryIs>
        <LinearRing>
          <coordinates>
            -122.2487354,37.5181339 -122.2481775,37.5121769
            -122.2457314,37.5086705 -122.2466755,37.5083982
            -122.2543144,37.5148321 -122.2537994,37.5157171
            -122.2509670,37.5177254 -122.2487354,37.5181339
          </coordinates>
        </LinearRing>
      </outerBoundaryIs>
    </Polygon>
  </Placemark>
</kml>
Creating the JSON Input File

Copy the following data to a file on the filesystem. Choose a location that is readable by your MarkLogic installation. You can use any file name, but the subsequent instructions assume 'geo-examples.json'.

After saving the data to a file, load the sample data into the database using the instructions in Loading the Sample Data.

{ "type": "FeatureCollection",
  "features": [
    { "type": "Feature",
      "geometry": {"type": "Point", "coordinates": [-122.2465038,37.5073428]},
      "properties": {"name": "MarkLogic HQ"}
    },
    { "type": "Feature",
      "geometry": {"type": "Point", "coordinates": [-122.2581983,37.5128407]},
      "properties": {"name": "Restaurant"}
    },
    { "type": "Feature",
      "geometry": {"type": "Point", "coordinates": [-122.2527051,37.5128917]},
      "properties": {"name": "Museum"}
    },
    { "type": "Feature",
      "geometry": {
        "type": "LineString",
        "coordinates": [
          [-122.2637558,37.5206187], [-122.2428131,37.5020318]
        ]
      },
      "properties": { "name": "Hwy 101" }
    },
    { "type": "Feature",
      "geometry": {
        "type": "LineString",
        "coordinates": [
          [-122.2598934,37.5096578], [-122.2551727,37.5148321],
          [-122.2536278,37.5172148], [-122.2523403,37.5185083],
          [-122.2520828,37.5202102]
        ]
      },
      "properties": { "name": "Holly St" }
    },
    { "type": "Feature",
      "geometry": {
        "type": "Polygon",
        "coordinates": [
          [
            [-122.2428131,37.5173510], [-122.2468472,37.5131641],
            [-122.2422123,37.5069683], [-122.2356892,37.5102365],
            [-122.2384787,37.5154788], [-122.2428131,37.5173510]
          ]
        ]
      },
      "properties": { "name": "Wildlife Refuge" }
    },
    { "type": "Feature",
      "geometry": {
        "type": "Polygon",
        "coordinates": [
          [
            [-122.2634554,37.5190870], [-122.2480488,37.5212994],
            [-122.2446156,37.5122790], [-122.2455597,37.5033596],
            [-122.2598076,37.5061853], [-122.2633696,37.5134364],
            [-122.2634554,37.5190870]
          ]
        ]
      },
      "properties": { "name": "MarkLogic Neighborhood" }
    },
    { "type": "Feature",
      "geometry": {
        "type": "Polygon",
        "coordinates": [
          [
            [-122.2485638,37.5033937], [-122.2465038,37.5015552],
            [-122.2478342,37.5003635], [-122.2502375,37.5022020],
            [-122.2485638,37.5033937]
          ]
        ]
      },
      "properties": { "name": "Shopping Center" }
    },
    { "type": "Feature",
      "geometry": {
        "type": "Polygon",
        "coordinates": [
          [
            [-122.2487354,37.5181339], [-122.2481775,37.5121769],
            [-122.2457314,37.5086705], [-122.2466755,37.5083982],
            [-122.2543144,37.5148321], [-122.2537994,37.5157171],
            [-122.2509670,37.5177254], [-122.2487354,37.5181339]
          ]
        ]
      },
      "properties": { "name": "Airport" }
    }
  ]
}

Loading the Sample Data

The procedures in this section load the raw data from Creating the Input Data Files into documents of the form discussed in Overview of the Sample Data.

This section uses XQuery to load the XML documents and Server-Side JavaScript to load the JSON documents. You could use either language to load both, but the XML transformations flow more naturally in XQuery, while the JSON transformations flow more naturally in JavaScript.

You can load either or both data sets, but some examples in this chapter will not work if you do not load both. You do not need to be familiar with either XQuery or JavaScript to follow the instructions in this section.

Loading the XML Sample Data

The procedure in this section uses XQuery to load the sample data because it is easier to do XML transformations using XQuery. You do not need to be familiar with XQuery to follow this procedure.

Before you begin, you should have completed the steps in Creating the Input Data Files.

  1. Open the Query Console tool in your browser. For example, if MarkLogic is installed on localhost, navigate to the following URL: http://localhost:8000.
  2. Copy the following query into a new query tab in Query Console:
    xquery version "1.0-ml";
    import module namespace geokml = "http://marklogic.com/geospatial/kml"
             at "/MarkLogic/geospatial/kml.xqy";
    declare namespace kml="http://www.opengis.net/kml/2.2";
    
    (: *** CHANGE THIS VAR VALUE TO MATCH YOUR ENV *** :)
    declare variable $INPUT-FILE := "/my/dir/geo-examples.xml";
    
    (: Convert the KML regions into cts regions :)
    declare function local:region-convert(
      $nodes as node()*
    ) as cts:region*
    {
      for $n in $nodes return
      typeswitch($n)
        case element(kml:Polygon) return geokml:parse-kml($n)
        case element(kml:LineString) return geokml:parse-kml($n)
        case element(kml:Point) return () (: return geokml:parse-kml($n) :)
        default return local:region-convert($n/node())
    };
    
    (: Create a doc for each KML Placemark, with a wrapper around
     : the KML that contains the cts region equiv of the KML region :)
    let $file := xdmp:document-get($INPUT-FILE)
    return
      for $place in $file//*:Placemark
      let $basename := fn:string-join(fn:tokenize($place/*:name, " "),"-")
      return xdmp:document-insert(
        fn:concat("/geo-examples/",$basename,".xml"),
        <envelope>{
           let $converted-region := local:region-convert($place)
           return 
             if (fn:empty($converted-region)) 
             then ()
             else <cts-region>{$converted-region}</cts-region>
        }
        {$place}
        </envelope>,
        xdmp:default-permissions(), ("geo-xml-examples", "geo-examples"))
  3. Modify the query to set the value of the $INPUT-FILE variable to the absolute path to the file containing the raw XML input data. This is the file you created in Creating the Input Data Files.
  4. Choose XQuery in the Query Type dropdown list.
  5. Choose the database into which you want to insert the documents in the Database dropdown list. For example, choose the Documents database.
  6. Click the Run button to evaluate the query and create documents in the database.
  7. Optionally, click the explorer icon to the right of the Database dropdown to explore the database contents and examine the new documents.

If the query is successful, the following documents are created. All the documents have a '/geo-examples/' directory prefix and are in collections named 'geo-xml-examples' and 'geo-examples'.

  • /geo-examples/Airport.xml
  • /geo-examples/Holly-St.xml
  • /geo-examples/Hwy-101.xml
  • /geo-examples/MarkLogic-HQ.xml
  • /geo-examples/MarkLogic-Neighborhood.xml
  • /geo-examples/Museum.xml
  • /geo-examples/Restaurant.xml
  • /geo-examples/Shopping-Center.xml
  • /geo-examples/Wildlife-Refuge.xml

For more information about the data, see Overview of the Sample Data.

Next, load the JSON sample documents using the instructions in Loading the JSON Sample Data.

Loading the JSON Sample Data

The procedure in this section uses Server-Side JavaScript to load the sample data because it is easier to do JSON transformations using JavaScript. You do not need to be familiar with JavaScript to follow this procedure.

  1. Open the Query Console tool in your browser. For example, if MarkLogic is installed on localhost, navigate to the following URL: http://localhost:8000.
  2. Copy the following query into a new query tab in Query Console:
    declareUpdate();
    const geojson = require('/MarkLogic/geospatial/geojson');
    
    // *** CHANGE FILE NAME TO MATCH YOUR ENV ***
    const inputFilename = '/my/dir/geo-examples.json';
    
    const rawData = fn.head(xdmp.documentGet(inputFilename)).toObject();
    for (let feature of rawData.features) {
      // replace whitespace in feature name with a dash
      const uri = '/geo-examples/' +
        feature.properties.name.replace(/\s+/g,'-') + '.json';
      const newDoc = { envelope: {feature: feature} };
      if (feature.geometry.type != "Point") {
        newDoc.envelope.ctsRegion =
          fn.head(geojson.parseGeojson(feature.geometry));
      }
      xdmp.documentInsert(
        uri, newDoc,
        xdmp.defaultPermissions(), 
        ['geo-json-examples', 'geo-examples']
      );
    }
  3. Modify the query to set the value of the $INPUT-FILE variable to the absolute path to the file containing the raw input data. This is the file you created in Creating the Input Data Files.
  4. Choose JavaScript in the Query Type dropdown list.
  5. Choose the database into which you want to insert the documents in the Database dropdown list. For example, choose the Documents database.
  6. Click the Run button to evaluate the query and create documents in the database.
  7. Optionally, click the explorer icon to the right of the Database dropdown to explore the database contents and examine the new documents.

If the query is successful, the following documents are created. All the documents have a '/geo-examples/' directory prefix and are in collections named 'geo-json-examples' and 'geo-examples'.

  • /geo-examples/Airport.json
  • /geo-examples/Holly-St.json
  • /geo-examples/Hwy-101.json
  • /geo-examples/MarkLogic-HQ.json
  • /geo-examples/MarkLogic-Neighborhood.json
  • /geo-examples/Museum.json
  • /geo-examples/Restaurant.json
  • /geo-examples/Shopping-Center.json
  • /geo-examples/Wildlife-Refuge.json

For more information about the data, see Overview of the Sample Data.

Your database is now properly configured to run the examples in this chapter.

« Previous chapter
Next chapter »
Powered by MarkLogic Server 7.0-4.1 and rundmc | Terms of Use | Privacy Policy