-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathoperators.json
More file actions
376 lines (376 loc) · 17.6 KB
/
Copy pathoperators.json
File metadata and controls
376 lines (376 loc) · 17.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
{
"count": 6,
"functions": [
{
"name": "&&",
"slug": "op-amp-amp",
"category": "operators",
"signatures": [
"geometry && geometry -> boolean",
"geography && geography -> boolean"
],
"summary": "Returns true when the 2D bounding boxes of the two arguments intersect. This is the raw index operator: it compares cached boxes, never the actual vertices, so it is cheap but approximate. Every GiST-accelerated predicate in PostGIS is internally a && test followed by an exact recheck, which is why && on its own is the fastest possible spatial filter and also the least precise one.",
"returns": "boolean",
"since": "1.0 (geography since 1.5)",
"arguments": [
{
"name": "A",
"type": "geometry",
"description": "Left operand; its cached 2D bounding box is used.",
"optional": false
},
{
"name": "B",
"type": "geometry",
"description": "Right operand; its cached 2D bounding box is used.",
"optional": false
}
],
"example": {
"sql": "SELECT 'LINESTRING(0 0, 10 10)'::geometry && 'POINT(9 1)'::geometry AS bbox_hit,\n ST_Intersects('LINESTRING(0 0, 10 10)'::geometry, 'POINT(9 1)'::geometry) AS real_hit;",
"result": " bbox_hit | real_hit\n----------+----------\n t | f",
"psycopg": "with conn.cursor() as cur:\n cur.execute(\n \"SELECT id FROM parcels WHERE geom && ST_MakeEnvelope(%s, %s, %s, %s, 3857)\",\n (xmin, ymin, xmax, ymax),\n )\n candidate_ids = [row[0] for row in cur]",
"geoalchemy": "from sqlalchemy import select\nfrom geoalchemy2.functions import ST_MakeEnvelope\n\nbox = ST_MakeEnvelope(xmin, ymin, xmax, ymax, 3857)\nstmt = select(Parcel.id).where(Parcel.geom.bool_op(\"&&\")(box))"
},
"srid_notes": "Both operands must share an SRID or PostGIS raises \"Operation on mixed SRID geometries\". The comparison is always planar and always in the units of that SRID, so on 4326 a box is degrees, not metres. Boxes of geographic data that cross the antimeridian wrap badly - split such geometries before indexing.",
"index_usage": {
"gist": true,
"sargable": true,
"needs_bbox_prefilter": false,
"notes": "This is the index operator itself. A GiST index on the left column is used directly whenever the right operand is a constant or a parameter."
},
"common_mistakes": [
"Treating && as a real intersection test. It only compares rectangles, so a diagonal line 'intersects' every point in its bounding box.",
"Mixing SRIDs between the operands, which errors out rather than silently reprojecting.",
"Adding && alongside ST_Intersects in the same WHERE clause. ST_Intersects already emits the && term itself, so the extra predicate only costs planning time."
],
"see_also": [
"ST_Intersects",
"ST_DWithin",
"ST_Expand",
"&&&",
"ST_MakeEnvelope"
],
"tags": [
"bbox",
"index",
"operator",
"gist",
"filter"
],
"docs_url": "https://www.postgis-python.com/mastering-core-spatial-query-patterns/",
"verify": {
"mode": "exact",
"reason": "",
"min_version": ""
}
},
{
"name": "&&&",
"slug": "op-amp-amp-amp",
"category": "operators",
"signatures": [
"geometry &&& geometry -> boolean"
],
"summary": "The n-dimensional counterpart of &&: it returns true when the n-D bounding boxes of the two geometries intersect, taking Z (and M, for the ND index) into account. You need it when a 2D box test would wrongly match features that are separated only in elevation, such as building floors or seismic horizons.",
"returns": "boolean",
"since": "2.0",
"arguments": [
{
"name": "A",
"type": "geometry",
"description": "Left operand; its n-D bounding box is used.",
"optional": false
},
{
"name": "B",
"type": "geometry",
"description": "Right operand; its n-D bounding box is used.",
"optional": false
}
],
"example": {
"sql": "SELECT 'LINESTRING Z (0 0 0, 1 1 1)'::geometry &&& 'LINESTRING Z (0 0 5, 1 1 6)'::geometry AS nd_hit,\n 'LINESTRING Z (0 0 0, 1 1 1)'::geometry && 'LINESTRING Z (0 0 5, 1 1 6)'::geometry AS twod_hit;",
"result": " nd_hit | twod_hit\n--------+----------\n f | t",
"psycopg": "with conn.cursor() as cur:\n cur.execute(\n \"SELECT id FROM boreholes WHERE geom &&& ST_3DMakeBox(%s::geometry, %s::geometry)\",\n (lower_corner_ewkt, upper_corner_ewkt),\n )\n hits = [row[0] for row in cur]",
"geoalchemy": "from sqlalchemy import select\n\nstmt = select(Borehole.id).where(Borehole.geom.bool_op(\"&&&\")(volume_geom))"
},
"srid_notes": "Same mixed-SRID rule as &&. Z values are in whatever unit the source data used and are never converted by ST_Transform, which only touches X/Y for most projections - so a table in metres horizontally may still hold feet vertically. Check before trusting a Z comparison.",
"index_usage": {
"gist": true,
"sargable": true,
"needs_bbox_prefilter": false,
"notes": "Requires an n-D index built with the ND operator class: CREATE INDEX ... USING gist (geom gist_geometry_ops_nd). The default 2D GiST index will not serve &&&."
},
"common_mistakes": [
"Expecting the default GiST index to serve it; you must create a gist_geometry_ops_nd index.",
"Using &&& on 2D data, where it degenerates to && with extra confusion.",
"Assuming ST_Transform reprojects Z, so comparing Z values across differently projected tables."
],
"see_also": [
"&&",
"ST_Force2D"
],
"tags": [
"bbox",
"3d",
"index",
"operator",
"nd"
],
"docs_url": "https://www.postgis-python.com/advanced-gist-indexing-optimization/",
"verify": {
"mode": "exact",
"reason": "",
"min_version": ""
}
},
{
"name": "<#>",
"slug": "op-lt-hash-gt",
"category": "operators",
"signatures": [
"geometry <#> geometry -> double precision"
],
"summary": "Returns the distance between the bounding boxes of two geometries, and like <-> it can drive an index-assisted ORDER BY. Because it compares boxes it is cheaper than <-> but only approximate; it returns 0 for any pair whose boxes overlap, so it cannot rank objects that are box-wise nested.",
"returns": "double precision",
"since": "2.0",
"arguments": [
{
"name": "A",
"type": "geometry",
"description": "Left operand; only its bounding box is considered.",
"optional": false
},
{
"name": "B",
"type": "geometry",
"description": "Right operand; only its bounding box is considered.",
"optional": false
}
],
"example": {
"sql": "SELECT 'LINESTRING(0 0, 1 1)'::geometry <#> 'POINT(4 0)'::geometry AS box_dist,\n ST_Distance('LINESTRING(0 0, 1 1)'::geometry, 'POINT(4 0)'::geometry) AS true_dist;",
"result": " box_dist | true_dist\n----------+--------------------\n 3 | 3.1622776601683795",
"psycopg": "with conn.cursor() as cur:\n cur.execute(\n \"\"\"\n SELECT id FROM tiles\n ORDER BY bbox <#> ST_SetSRID(ST_MakePoint(%s, %s), 3857)\n LIMIT 50\n \"\"\",\n (x, y),\n )\n coarse_candidates = [row[0] for row in cur]",
"geoalchemy": "from sqlalchemy import select\nfrom geoalchemy2.functions import ST_MakePoint, ST_SetSRID\n\nhere = ST_SetSRID(ST_MakePoint(x, y), 3857)\nstmt = select(Tile.id).order_by(Tile.bbox.distance_box(here)).limit(50)"
},
"srid_notes": "Planar and in SRID units, exactly like &&. There is no geography form of <#>. If your data is in 4326 the box distance is in degrees and is anisotropic away from the equator, which makes it a poor ranking key over large latitude ranges.",
"index_usage": {
"gist": true,
"sargable": true,
"needs_bbox_prefilter": false,
"notes": "Index-assisted in ORDER BY with LIMIT, same conditions as <->. Useful as a cheap first pass over large polygons, then re-rank the shortlist with ST_Distance."
},
"common_mistakes": [
"Expecting true distance. Two long overlapping-box geometries both score 0 and become unorderable.",
"Using it on point data, where it is identical to <-> but less obvious to the next reader.",
"Assuming a geography variant exists; it does not."
],
"see_also": [
"<->",
"&&",
"ST_Distance"
],
"tags": [
"knn",
"bbox",
"distance",
"order-by",
"index",
"operator"
],
"docs_url": null,
"verify": {
"mode": "exact",
"reason": "",
"min_version": ""
}
},
{
"name": "<->",
"slug": "op-lt-minus-gt",
"category": "operators",
"signatures": [
"geometry <-> geometry -> double precision",
"geography <-> geography -> double precision"
],
"summary": "The KNN distance operator. In an ORDER BY it lets a GiST index return rows in order of increasing distance from a reference point, which is the only way to get a nearest-neighbour query that does not read the whole table. Since PostGIS 2.2 on PostgreSQL 9.5+ it returns true geometry-to-geometry distance, not box distance.",
"returns": "double precision",
"since": "2.0 (true distance since 2.2)",
"arguments": [
{
"name": "A",
"type": "geometry",
"description": "Left operand. For index use, this must be the indexed column.",
"optional": false
},
{
"name": "B",
"type": "geometry",
"description": "Right operand, normally a constant reference geometry.",
"optional": false
}
],
"example": {
"sql": "SELECT ROUND(('POINT(0 0)'::geometry <-> 'POINT(3 4)'::geometry)::numeric, 3) AS d;",
"result": " d\n-------\n 5.000",
"psycopg": "with conn.cursor() as cur:\n cur.execute(\n \"\"\"\n SELECT id, name, geom <-> ST_SetSRID(ST_MakePoint(%s, %s), 4326) AS dist\n FROM stops\n ORDER BY geom <-> ST_SetSRID(ST_MakePoint(%s, %s), 4326)\n LIMIT 10\n \"\"\",\n (lon, lat, lon, lat),\n )\n nearest = cur.fetchall()",
"geoalchemy": "from sqlalchemy import select\nfrom geoalchemy2.functions import ST_MakePoint, ST_SetSRID\n\nhere = ST_SetSRID(ST_MakePoint(lon, lat), 4326)\nstmt = select(Stop.id, Stop.name).order_by(Stop.geom.distance_centroid(here)).limit(10)"
},
"srid_notes": "On geometry the result is in SRID units, so on 4326 you get degrees - useless as a distance but perfectly valid as an ordering key at small extents. For metres either cast both sides to geography (the geography <-> is also index-assisted) or store a projected column. Ordering by degrees and ordering by metres differ noticeably at high latitudes.",
"index_usage": {
"gist": true,
"sargable": true,
"needs_bbox_prefilter": false,
"notes": "Index-assisted only in an ORDER BY with a LIMIT, where one side is a constant and the other is the indexed column. Put it in a WHERE clause and you get a sequential scan; use ST_DWithin there instead."
},
"common_mistakes": [
"Using <-> in WHERE (geom <-> point < 1000). That is not index-assisted - use ST_DWithin for a radius filter.",
"Omitting LIMIT, which makes the planner prefer a sort over the index scan and reads every row.",
"Reading the geometry result as metres on SRID 4326, where it is degrees."
],
"see_also": [
"<#>",
"ST_DWithin",
"ST_Distance",
"ST_ClosestPoint"
],
"tags": [
"knn",
"nearest-neighbour",
"distance",
"order-by",
"index",
"operator"
],
"docs_url": "https://www.postgis-python.com/mastering-core-spatial-query-patterns/",
"verify": {
"mode": "exact",
"reason": "",
"min_version": ""
}
},
{
"name": "@",
"slug": "op-at",
"category": "operators",
"signatures": [
"geometry @ geometry -> boolean"
],
"summary": "Returns true when the bounding box of A is completely contained by the bounding box of B. It is exactly the ~ operator with the operands reversed, provided so that the indexed column can stay on the left in either direction of a containment query.",
"returns": "boolean",
"since": "1.0",
"arguments": [
{
"name": "A",
"type": "geometry",
"description": "Candidate contained geometry; its box must fall inside B's.",
"optional": false
},
{
"name": "B",
"type": "geometry",
"description": "Candidate container.",
"optional": false
}
],
"example": {
"sql": "SELECT 'POINT(5 5)'::geometry @ 'POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))'::geometry AS inside_box;",
"result": " inside_box\n------------\n t",
"psycopg": "with conn.cursor() as cur:\n cur.execute(\n \"SELECT count(*) FROM trees WHERE geom @ ST_MakeEnvelope(%s, %s, %s, %s, 27700)\",\n (xmin, ymin, xmax, ymax),\n )\n (count,) = cur.fetchone()",
"geoalchemy": "from sqlalchemy import func, select\nfrom geoalchemy2.functions import ST_MakeEnvelope\n\nbox = ST_MakeEnvelope(xmin, ymin, xmax, ymax, 27700)\nstmt = select(func.count()).select_from(Tree).where(Tree.geom.bool_op(\"@\")(box))"
},
"srid_notes": "Planar, SRID units, mixed SRIDs error. For a viewport query the envelope you build must be in the same SRID as the column - build it with ST_MakeEnvelope(..., srid) rather than transforming the column, which would defeat the index.",
"index_usage": {
"gist": true,
"sargable": true,
"needs_bbox_prefilter": false,
"notes": "Index-assisted on the left operand, which makes it the natural spelling for \"features inside this viewport\" against an indexed geometry column."
},
"common_mistakes": [
"Swapping the operands, which silently returns the wrong rows rather than erroring.",
"Wrapping the indexed column in ST_Transform to match the envelope's SRID, which makes the index unusable.",
"Expecting exact containment; only the boxes are compared."
],
"see_also": [
"~",
"ST_Within",
"ST_MakeEnvelope"
],
"tags": [
"bbox",
"within",
"viewport",
"index",
"operator"
],
"docs_url": "https://www.postgis-python.com/advanced-gist-indexing-optimization/",
"verify": {
"mode": "exact",
"reason": "",
"min_version": ""
}
},
{
"name": "~",
"slug": "op-tilde",
"category": "operators",
"signatures": [
"geometry ~ geometry -> boolean"
],
"summary": "Returns true when the bounding box of A completely contains the bounding box of B. It is the box-level analogue of ST_Contains and is occasionally useful as a hand-written prefilter for containment queries against very complex polygons, where the exact test is expensive.",
"returns": "boolean",
"since": "1.0",
"arguments": [
{
"name": "A",
"type": "geometry",
"description": "Candidate container; its bounding box must enclose B's.",
"optional": false
},
{
"name": "B",
"type": "geometry",
"description": "Candidate contained geometry.",
"optional": false
}
],
"example": {
"sql": "SELECT 'POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))'::geometry ~ 'POINT(5 5)'::geometry AS box_contains;",
"result": " box_contains\n--------------\n t",
"psycopg": "with conn.cursor() as cur:\n cur.execute(\n \"SELECT id FROM regions WHERE geom ~ %s::geometry ORDER BY ST_Area(geom) LIMIT 1\",\n (point_ewkt,),\n )\n smallest_enclosing = cur.fetchone()",
"geoalchemy": "from sqlalchemy import select\n\nstmt = select(Region.id).where(Region.geom.bool_op(\"~\")(point_element))"
},
"srid_notes": "Planar box comparison in SRID units, with the usual mixed-SRID error. Note that box containment is not geometry containment: a concave polygon's box contains points that lie outside the polygon itself.",
"index_usage": {
"gist": true,
"sargable": true,
"needs_bbox_prefilter": false,
"notes": "Directly index-assisted on the left operand. ST_Contains already generates this internally, so writing it yourself is only worthwhile in hand-tuned two-stage queries."
},
"common_mistakes": [
"Reading it as real containment. A crescent-shaped polygon's box contains the hole.",
"Confusing it with the PostgreSQL regex-match operator of the same spelling on text.",
"Getting the operand order backwards; ~ means 'A's box contains B's box', @ is the reverse."
],
"see_also": [
"@",
"ST_Contains",
"&&"
],
"tags": [
"bbox",
"contains",
"index",
"operator"
],
"docs_url": null,
"verify": {
"mode": "exact",
"reason": "",
"min_version": ""
}
}
]
}