Skip to content

Commit ec5495a

Browse files
committed
Support to SQL for database in other SRID than 4326
1 parent 0937495 commit ec5495a

File tree

16 files changed

+150
-58
lines changed

16 files changed

+150
-58
lines changed

.rubocop.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ Layout/LineLength:
1212
Style/Documentation:
1313
Enabled: false
1414

15+
Style/StringConcatenation:
16+
Enabled: false
17+
1518
Metrics/AbcSize:
1619
Max: 40
1720

lib/overpass_parser/nodes/filters.rb

Lines changed: 46 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -23,35 +23,61 @@ class Filter < T::Struct
2323
const :area_id, T.nilable(String)
2424
const :around, T.nilable(FilterAround)
2525

26-
def bbox_clauses(sql_dialect)
27-
"#{sql_dialect.st_intersects_extent}(" \
28-
"ST_Envelope('SRID=4326;LINESTRING(" \
29-
"#{T.must(bbox)[1]} #{T.must(bbox)[0]}, " \
30-
"#{T.must(bbox)[3]} #{T.must(bbox)[2]}" \
31-
")'::geometry), geom)"
26+
def bbox_clauses(sql_dialect, srid)
27+
"#{sql_dialect.st_intersects_extent}(" +
28+
sql_dialect.st_transform(
29+
"ST_Envelope('SRID=4326;LINESTRING(" \
30+
"#{T.must(bbox)[1]} #{T.must(bbox)[0]}, " \
31+
"#{T.must(bbox)[3]} #{T.must(bbox)[2]}" \
32+
")'::geometry)", srid
33+
) + ', geom)'
3234
end
3335

34-
def poly_clauses(sql_dialect)
36+
def poly_clauses(sql_dialect, srid)
3537
coords = T.must(poly).collect do |lat, lon|
3638
"#{lon} #{lat}"
3739
end.join(', ')
38-
"#{sql_dialect.st_intersects}('SRID=4326;POLYGON(#{coords})'::geometry, geom)"
40+
"#{sql_dialect.st_intersects}(" +
41+
sql_dialect.st_transform("'SRID=4326;POLYGON(#{coords})'::geometry", srid) +
42+
', geom)'
43+
end
44+
45+
def around_clause(sql_dialect, srid)
46+
# Implementing Within clause using buffer in meters in the best UTM zone
47+
"ST_Intersects(
48+
geom,
49+
" + sql_dialect.st_transform("
50+
ST_Buffer(
51+
" + sql_dialect.st_transform("
52+
(SELECT #{sql_dialect.st_union}(geom) FROM _#{around&.core})",
53+
"
54+
-- Calculate UTM zone from
55+
32600 +
56+
CASE WHEN ST_Y(ST_Centroid(
57+
(SELECT #{sql_dialect.st_union}(geom) FROM _#{around&.core})
58+
)) >= 0 THEN 1 ELSE 31 END +
59+
floor(ST_X(ST_Centroid(
60+
(SELECT #{sql_dialect.st_union}(geom) FROM _#{around&.core})
61+
) + 180) / 6)
62+
") + ",
63+
#{around&.radius}
64+
)", srid) + "
65+
)"
3966
end
4067

4168
sig do
4269
params(
43-
sql_dialect: SqlDialect::SqlDialect
70+
sql_dialect: SqlDialect::SqlDialect,
71+
srid: Integer
4472
).returns(T.nilable(String))
4573
end
46-
def to_sql(sql_dialect)
74+
def to_sql(sql_dialect, srid)
4775
clauses = []
48-
clauses << bbox_clauses(sql_dialect) unless bbox.nil?
49-
clauses << poly_clauses(sql_dialect) unless poly.nil?
76+
clauses << bbox_clauses(sql_dialect, srid) unless bbox.nil?
77+
clauses << poly_clauses(sql_dialect, srid) unless poly.nil?
5078
clauses << "id = ANY (ARRAY[#{ids&.collect(&:to_s)&.join(', ')}])" unless ids.nil?
5179
clauses << "ST_Intersects(geom, (SELECT #{sql_dialect.st_union}(geom) FROM _#{area_id}))" unless area_id.nil?
52-
unless around.nil?
53-
clauses << "ST_Within(geom, (SELECT #{sql_dialect.st_union}(geom) FROM _#{around&.core}), #{around&.radius})"
54-
end
80+
clauses << around_clause(sql_dialect, srid) unless around.nil?
5581

5682
return nil if clauses.empty?
5783

@@ -64,16 +90,17 @@ class Filters < Array
6490

6591
sig do
6692
params(
67-
sql_dialect: SqlDialect::SqlDialect
93+
sql_dialect: SqlDialect::SqlDialect,
94+
srid: Integer
6895
).returns(T.nilable(String))
6996
end
70-
def to_sql(sql_dialect)
97+
def to_sql(sql_dialect, srid)
7198
if empty?
7299
nil
73100
elsif size == 1
74-
self[0].to_sql(sql_dialect)
101+
self[0].to_sql(sql_dialect, srid)
75102
else
76-
collect { |s| s.to_sql(sql_dialect) }.join(' AND ')
103+
collect { |s| s.to_sql(sql_dialect, srid) }.join(' AND ')
77104
end
78105
end
79106
end

lib/overpass_parser/nodes/out.rb

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ def to_sql(sql_dialect)
2828
#{sql_dialect.json_strip_nulls}(#{sql_dialect.json_build_object}(
2929
'type', CASE osm_type WHEN 'n' THEN 'node' WHEN 'w' THEN 'way' WHEN 'r' THEN 'relation' WHEN 'a' THEN 'area' END,
3030
'id', id,
31-
'lon', CASE osm_type WHEN 'n' THEN ST_X(geom)::numeric END,
32-
'lat', CASE osm_type WHEN 'n' THEN ST_Y(geom)::numeric END\
31+
'lon', CASE osm_type WHEN 'n' THEN ST_X(ST_Transform(geom, 4326))::numeric END,
32+
'lat', CASE osm_type WHEN 'n' THEN ST_Y(ST_Transform(geom, 4326))::numeric END\
3333
#{meta ? ",\n 'timestamp', created" : ''}\
3434
#{meta ? ",\n 'version', version" : ''}\
3535
#{meta ? ",\n 'changeset', changeset" : ''}\
@@ -39,8 +39,8 @@ def to_sql(sql_dialect)
3939
",
4040
'center', CASE osm_type = 'w' OR osm_type = 'r'
4141
WHEN true THEN #{sql_dialect.json_build_object}(
42-
'lon', ST_X(ST_PointOnSurface(geom))::numeric,
43-
'lat', ST_Y(ST_PointOnSurface(geom))::numeric
42+
'lon', ST_X(ST_PointOnSurface(ST_Transform(geom, 4326)))::numeric,
43+
'lat', ST_Y(ST_PointOnSurface(ST_Transform(geom, 4326)))::numeric
4444
)
4545
END"
4646
else
@@ -50,10 +50,10 @@ def to_sql(sql_dialect)
5050
",
5151
'bounds', CASE osm_type = 'w' OR osm_type = 'r'
5252
WHEN true THEN #{sql_dialect.json_build_object}(
53-
'minlon', ST_XMin(ST_Envelope(geom))::numeric,
54-
'minlat', ST_YMin(ST_Envelope(geom))::numeric,
55-
'maxlon', ST_XMax(ST_Envelope(geom))::numeric,
56-
'maxlat', ST_YMax(ST_Envelope(geom))::numeric
53+
'minlon', ST_XMin(ST_Envelope(ST_Transform(geom, 4326)))::numeric,
54+
'minlat', ST_YMin(ST_Envelope(ST_Transform(geom, 4326)))::numeric,
55+
'maxlon', ST_XMax(ST_Envelope(ST_Transform(geom, 4326)))::numeric,
56+
'maxlat', ST_YMax(ST_Envelope(ST_Transform(geom, 4326)))::numeric
5757
)
5858
END"
5959
end}\
@@ -63,13 +63,15 @@ def to_sql(sql_dialect)
6363
WHEN 'w' THEN " + (
6464
if sql_dialect.st_dump_points
6565
"(SELECT \
66-
#{sql_dialect.jsonb_agg}(#{sql_dialect.json_build_object}('lon', ST_X(geom)::numeric, 'lat', ST_Y(geom)::numeric)) \
66+
#{sql_dialect.jsonb_agg}(#{sql_dialect.json_build_object}(\
67+
'lon', ST_X(ST_Transform(geom, 4326))::numeric, \
68+
'lat', ST_Y(ST_Transform(geom, 4326))::numeric)) \
6769
FROM #{sql_dialect.st_dump_points}(geom))"
6870
else
6971
"replace(replace(replace(replace(replace((
7072
CASE ST_GeometryType(geom)
71-
WHEN 'LINESTRING' THEN ST_AsGeoJson(geom, 7)->'coordinates'
72-
ELSE ST_AsGeoJson(geom, 7)->'coordinates'->0
73+
WHEN 'LINESTRING' THEN ST_AsGeoJson(ST_Transform(geom, 4326), 7)->'coordinates'
74+
ELSE ST_AsGeoJson(ST_Transform(geom, 4326), 7)->'coordinates'->0
7375
END
7476
)::text, '[', '{\"lon\":'), \
7577
',', ',\"lat\":'), \

lib/overpass_parser/nodes/query_objects.rb

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,11 @@ def initialize(object_type:, selectors: nil, filters: nil, set: nil, asignation:
3131
sig do
3232
params(
3333
sql_dialect: SqlDialect::SqlDialect,
34+
srid: Integer,
3435
default_set: T.nilable(String)
3536
).returns(String)
3637
end
37-
def to_sql(sql_dialect, default_set)
38+
def to_sql(sql_dialect, srid, default_set)
3839
from = (
3940
if set.nil?
4041
object_type
@@ -53,7 +54,7 @@ def to_sql(sql_dialect, default_set)
5354
"osm_type = '#{object_type[0]}'"
5455
end,
5556
selectors&.to_sql(sql_dialect) || nil,
56-
filters&.to_sql(sql_dialect) || nil
57+
filters&.to_sql(sql_dialect, srid) || nil
5758
].compact.join(" AND\n ")
5859

5960
sql = "SELECT

lib/overpass_parser/nodes/query_recurse.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,11 @@ def initialize(recurse:, asignation: nil)
2525
sig do
2626
params(
2727
_sql_dialect: SqlDialect::SqlDialect,
28+
_srid: Integer,
2829
default_set: T.nilable(String)
2930
).returns(String)
3031
end
31-
def to_sql(_sql_dialect, default_set)
32+
def to_sql(_sql_dialect, _srid, default_set)
3233
"SELECT
3334
way.*
3435
FROM

lib/overpass_parser/nodes/query_union.rb

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,14 @@ def initialize(queries:, asignation: nil)
2626
sig do
2727
params(
2828
sql_dialect: SqlDialect::SqlDialect,
29+
srid: Integer,
2930
_default_set: T.nilable(String)
3031
).returns(String)
3132
end
32-
def to_sql(sql_dialect, _default_set)
33+
def to_sql(sql_dialect, srid, _default_set)
3334
default_set = T.let(nil, T.nilable(String))
3435
with = queries.collect do |querie|
35-
sql = querie.to_sql(sql_dialect, default_set).gsub(/^/, ' ')
36+
sql = querie.to_sql(sql_dialect, srid, default_set).gsub(/^/, ' ')
3637
default_set = querie.asignation
3738
"#{querie.asignation} AS (\n#{sql}\n)"
3839
end.join(",\n")

lib/overpass_parser/nodes/request.rb

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,14 @@ class Request < T::Struct
1919
sig do
2020
params(
2121
sql_dialect: SqlDialect::SqlDialect,
22+
srid: Integer,
2223
finalizer: T.nilable(String)
2324
).returns(String)
2425
end
25-
def to_sql(sql_dialect, finalizer = nil)
26+
def to_sql(sql_dialect, srid, finalizer = nil)
2627
default_set = T.let(nil, T.nilable(String))
2728
with = queries.collect do |querie|
28-
sql = querie.to_sql(sql_dialect, default_set).gsub(/^/, ' ')
29+
sql = querie.to_sql(sql_dialect, srid, default_set).gsub(/^/, ' ')
2930
default_set = querie.asignation
3031
"#{querie.asignation} AS (\n#{sql}\n)"
3132
end

lib/overpass_parser/sql_dialect/duckdb.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,20 @@ def st_intersects
8181
def st_intersects_extent
8282
'ST_Intersects_Extent'
8383
end
84+
85+
sig do
86+
params(geom: String, srid: T.any(Integer, String)).returns(String)
87+
end
88+
def st_transform(geom, srid)
89+
"ST_Transform(#{geom}, 'EPSG:4326', 'EPSG:#{srid}')"
90+
end
91+
92+
sig do
93+
params(geom: String, srid: T.any(Integer, String)).returns(String)
94+
end
95+
def st_transform_reverse(geom, srid)
96+
"ST_Transform(#{geom}, 'EPSG:#{srid}', 'EPSG:4326')"
97+
end
8498
end
8599
end
86100
end

lib/overpass_parser/sql_dialect/postgres.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,20 @@ def st_intersects
9898
def st_intersects_extent
9999
'ST_Intersects'
100100
end
101+
102+
sig do
103+
params(geom: String, srid: T.any(Integer, String)).returns(String)
104+
end
105+
def st_transform(geom, srid)
106+
"ST_Transform(#{geom}, #{srid})"
107+
end
108+
109+
sig do
110+
params(geom: String, _srid: T.any(Integer, String)).returns(String)
111+
end
112+
def st_transform_reverse(geom, _srid)
113+
"ST_Transform(#{geom}, 4326)"
114+
end
101115
end
102116
end
103117
end

lib/overpass_parser/sql_dialect/sql_dialect.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,16 @@ def st_intersects = ''
6565
returns(String)
6666
end
6767
def st_intersects_extent = ''
68+
69+
sig do
70+
params(_geom: String, _srid: T.any(Integer, String)).returns(String)
71+
end
72+
def st_transform(_geom, _srid) = ''
73+
74+
sig do
75+
params(_geom: String, _srid: T.any(Integer, String)).returns(String)
76+
end
77+
def st_transform_reverse(_geom, _srid) = ''
6878
end
6979
end
7080
end

0 commit comments

Comments
 (0)