├── .clang-format
├── .gitignore
├── CMakeLists.txt
├── LICENSE
├── Makefile
├── README-CN.md
├── README.md
├── imgs
└── tairgis_logo.png
├── src
├── CMakeLists.txt
├── redismodule.h
├── spatial.c
├── spatial.h
├── spatial
│ ├── .gitignore
│ ├── Makefile
│ ├── bing.c
│ ├── bing.h
│ ├── geom.c
│ ├── geom.h
│ ├── geom_json.c
│ ├── geom_levels.c
│ ├── geom_polymap.c
│ ├── geom_test.c
│ ├── geoutil.c
│ ├── geoutil.h
│ ├── geoutil_test.c
│ ├── grisu3.c
│ ├── grisu3.h
│ ├── hash.c
│ ├── hash.h
│ ├── json.c
│ ├── json.h
│ ├── poly.c
│ ├── poly.h
│ ├── poly_test.c
│ ├── polyinside.c
│ ├── polyinside_test.c
│ ├── polyintersects.c
│ ├── polyintersects_test.c
│ ├── polyraycast.c
│ ├── rtree.c
│ ├── rtree.h
│ ├── rtree_test.c
│ ├── rtree_tmpl.c
│ ├── test.c
│ ├── test.h
│ └── zmalloc.h
├── tairgis.c
├── tairgis.h
├── util.c
└── util.h
└── tests
└── tairgis.tcl
/.clang-format:
--------------------------------------------------------------------------------
1 | # We'll use defaults from the Google style, but with 4 columns indentation.
2 | BasedOnStyle: Google
3 | Language: Cpp
4 | IndentWidth: 4
5 | DerivePointerAlignment: false
6 | PointerAlignment: Right
7 | AccessModifierOffset: -4
8 | BreakBeforeBinaryOperators: All
9 | ColumnLimit: 0
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.so
2 | *.o
3 | build/
4 | cmake-build-debug/
5 | .idea/
--------------------------------------------------------------------------------
/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.0)
2 | project(tairgis_module)
3 |
4 | set(CMAKE_C_STANDARD 99)
5 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -W -Wall -g -ggdb -std=c99 -O2 -Wno-strict-aliasing -Wno-typedef-redefinition -Wno-sign-compare -Wno-unused-parameter")
6 | if (CMAKE_SYSTEM_NAME MATCHES "Linux")
7 | if (GCOV_MODE)
8 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fprofile-arcs -ftest-coverage")
9 | endif()
10 | endif ()
11 |
12 | add_subdirectory(src)
13 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
2 |
3 | COMPAT_MODULE = tairgis.so
4 | all: $(COMPAT_MODULE)
5 |
6 | COMPAT_DIR := build
7 | TOPDIR := $(shell pwd)
8 |
9 | ifeq ($(GCOV_MODE),TRUE)
10 | MODULE_FLAG = -DGCOV_MODE=TRUE
11 | endif
12 |
13 | $(COMPAT_MODULE): $(COMPAT_DIR)/tairgis.so
14 | cp $^ $@
15 |
16 | $(COMPAT_DIR)/tairgis.so:
17 | rm -fr $(COMPAT_DIR)
18 | mkdir $(COMPAT_DIR) && cd $(COMPAT_DIR) && cmake .. ${MODULE_FLAG}
19 | $(MAKE) -C $(COMPAT_DIR)
20 |
21 | ifeq ($(uname_S),Linux)
22 | cp $(COMPAT_DIR)/src/libtairgis.so $(COMPAT_DIR)/tairgis.so
23 | else
24 | cp $(COMPAT_DIR)/src/libtairgis.dylib $(COMPAT_DIR)/tairgis.so
25 | endif
26 |
27 | clean:
28 | -rm -fr $(COMPAT_MODULE) $(COMPAT_DIR)
29 |
--------------------------------------------------------------------------------
/README-CN.md:
--------------------------------------------------------------------------------
1 |
2 |

3 |
4 |
5 | ## 简介 [English introduction](README.md)
6 | TairGis 是一个 Redis Module,支持点、线、面之间相交、包含、被包含关系的判断,具有以下特点:
7 | - 查询性能快
8 | - 使用`RTree`存储
9 | - 通过`GIS.SEARCH`可实现Redis`GEORADIUS`命令的功能
10 |
11 | ## 感谢
12 | 特别感谢 https://github.com/tidwall/redis-gis, TairGis 依赖 redis-gis,并修复了其部分问题。
13 |
14 | ## 快速开始
15 | ```
16 | // 插入一个 POLYGON
17 | 127.0.0.1:6379> GIS.ADD hangzhou campus 'POLYGON ((120.028041 30.285179, 120.031203 30.28691, 120.037311 30.286239, 120.034185 30.280844))'
18 | (integer) 1
19 | // 获取 POLYGON
20 | 127.0.0.1:6379> GIS.GET hangzhou campus
21 | "POLYGON((120.028041 30.285179,120.031203 30.28691,120.037311 30.286239,120.034185 30.280844))"
22 | // 判断点 'POINT (120.031939 30.285179)' 和 POLYGON 是否包含
23 | 127.0.0.1:6379> GIS.CONTAINS hangzhou 'POINT (120.031939 30.285179)'
24 | 1) (integer) 1
25 | 2) 1) "campus"
26 | 2) "POLYGON((120.028041 30.285179,120.031203 30.28691,120.037311 30.286239,120.034185 30.280844))"
27 | // 判断线和 POLYGON 是否相交
28 | 127.0.0.1:6379> GIS.INTERSECTS hangzhou 'LINESTRING (120.029622 30.288952, 120.035488 30.280056)'
29 | 1) (integer) 1
30 | 2) 1) "campus"
31 | 2) "POLYGON((120.028041 30.285179,120.031203 30.28691,120.037311 30.286239,120.034185 30.280844))"
32 | 127.0.0.1:6379>
33 | ```
34 |
35 | ## 编译及使用
36 | ```
37 | make
38 | ```
39 | 编译成功后,会在当前目录下产生`tairgis.so`文件。
40 | ```
41 | ./redis-server --loadmodule /path/to/tairgis.so
42 | ```
43 |
44 | ## 测试方法
45 | 修改 tests 目录下 tairgis.tcl 文件中的路径为:`set testmodule [file your_path/tairgis.so]`
46 |
47 | 将 tests 目录下 tairgis.tcl 拷贝至 redis 目录 tests 下
48 | ```
49 | cp tests/tairgis.tcl your_redis_path/tests
50 | ```
51 | 将 tairgis 添加到 redis 的 test_helper.tcl 的 all_tests 中
52 | ```
53 | diff --git a/tests/test_helper.tcl b/tests/test_helper.tcl
54 | index 1a5096937..d5a1ba40a 100644
55 | --- a/tests/test_helper.tcl
56 | +++ b/tests/test_helper.tcl
57 | @@ -13,6 +13,7 @@ source tests/support/test.tcl
58 | source tests/support/util.tcl
59 |
60 | set ::all_tests {
61 | + tairgis
62 | ```
63 | 在redis根目录下运行 ./runtest --single tairgis
64 |
65 | ## 客户端
66 | | language | GitHub |
67 | |----------|---|
68 | | Java |https://github.com/alibaba/alibabacloud-tairjedis-sdk|
69 | | Python |https://github.com/alibaba/tair-py|
70 | | Go |https://github.com/alibaba/tair-go|
71 | | .Net |https://github.com/alibaba/AlibabaCloud.TairSDK|
72 |
73 | ## API
74 |
75 | ### GIS.ADD
76 | #### 语法及复杂度
77 | > GIS.ADD area polygonName polygonWkt [polygonName polygonWkt ...]
78 | > 时间复杂度:O(log n)
79 |
80 | #### 命令描述
81 | > 在area中添加指定名称的多边形(可添加多个),使用WKT(Well-known text)描述。WKT是一种文本标记语言,用于描述矢量几何对象、
82 | > 空间参照系统及空间参照系统之间的转换。
83 |
84 | #### 参数描述
85 | > area:一个几何概念。
86 | > polygonName:多边形的名称。
87 | > polygonWkt:多边形的描述信息,表示现实世界的经、纬度,使用WKT(Well-known text)描述,支持如下类型。
88 | > - POINT:描述一个点的WKT信息,例如'POINT (120.086631 30.138141)',表示该POINT位于经度120.086631,纬度30.138141。
89 | > - LINESTRING:描述一条线的WKT信息,由两个POINT组成,例如'LINESTRING (30 10, 40 40)'。
90 | > - POLYGON:描述一个多边形的WKT信息,由多个POINT组成,例如'POLYGON ((31 20, 29 20, 29 21, 31 31))'。
91 | > 说明:经度的取值范围为(-180,180), 纬度的取值范围为(-90,90)。不支持如下集合类型:MULTIPOINT、MULTILINESTRING、MULTIPOLYGON、GEOMETRY和COLLECTION。
92 |
93 | #### 返回值
94 | > 执行成功:返回插入和更新成功的多边形数量。
95 | > 其它情况返回相应的异常信息。
96 |
97 | #### 示例
98 | ```
99 | 127.0.0.1:6379> GIS.ADD hangzhou campus 'POLYGON ((30 10, 40 40, 20 40, 10 20, 30 10))'
100 | (integer) 1
101 | 127.0.0.1:6379>
102 | ```
103 |
104 | ### GIS.GET
105 | #### 语法及复杂度
106 | > GIS.ADD area polygonName
107 | > 时间复杂度:O(1)
108 |
109 | #### 命令描述
110 | > 获取目标area中指定多边形的WKT信息。
111 |
112 | #### 参数描述
113 | > area:一个几何概念。
114 | > polygonName:多边形的名称。
115 |
116 | #### 返回值
117 | > 执行成功:WKT信息。
118 | > area或polygonName不存在:nil。
119 | > 其它情况返回相应的异常信息。
120 |
121 | #### 示例
122 | ```
123 | 提前执行 GIS.ADD hangzhou campus 'POLYGON ((30 10, 40 40, 20 40, 10 20, 30 10))'命令
124 |
125 | 127.0.0.1:6379> GIS.GET hangzhou campus
126 | "POLYGON((30 10,40 40,20 40,10 20,30 10))"
127 | 127.0.0.1:6379>
128 | ```
129 |
130 | ### GIS.GETALL
131 | #### 语法及复杂度
132 | > GIS.ADD area [WITHOUTWKT]
133 | > 时间复杂度:O(n)
134 |
135 | #### 命令描述
136 | > 获取目标area中所有多边形的名称和WKT信息。如果设置了WITHOUTWKT选项,仅返回多边形的名称。
137 |
138 | #### 参数描述
139 | > area:一个几何概念。
140 | > WITHOUTWKT:用于控制是否返回多边形的WKT信息,如果加上该参数,则不返回多边形的WKT信息。
141 |
142 | #### 返回值
143 | > 执行成功:返回多边形名称和WKT信息,如果设置了WITHOUTWKT选项,仅返回多边形的名称。
144 | > area不存在:nil。
145 | > 其它情况返回相应的异常信息。
146 |
147 | #### 示例
148 | ```
149 | 提前执行 GIS.ADD hangzhou campus 'POLYGON ((30 10, 40 40, 20 40, 10 20, 30 10))'命令
150 |
151 | 127.0.0.1:6379> GIS.GETALL hangzhou
152 | 1) "campus"
153 | 2) "POLYGON((30 10,40 40,20 40,10 20,30 10))"
154 | 127.0.0.1:6379>
155 | ```
156 |
157 | ### GIS.DEL
158 | #### 语法及复杂度
159 | > GIS.DEL area polygonName
160 | > 时间复杂度:O(log n)
161 |
162 | #### 命令描述
163 | > 删除目标area中指定的多边形。
164 |
165 | #### 参数描述
166 | > area:一个几何概念。
167 | > polygonName:多边形的名称。
168 |
169 | #### 返回值
170 | > 执行成功:OK。
171 | > area或polygonName不存在:nil。
172 | > 其它情况返回相应的异常信息。
173 |
174 | #### 示例
175 | ```
176 | 提前执行 GIS.ADD hangzhou campus 'POLYGON ((30 10, 40 40, 20 40, 10 20, 30 10))'命令
177 |
178 | 127.0.0.1:6379> GIS.DEL hangzhou campus
179 | OK
180 | 127.0.0.1:6379>
181 | ```
182 |
183 | ### GIS.CONTAINS
184 | #### 语法及复杂度
185 | > GIS.CONTAINS area polygonWkt [WITHOUTWKT]
186 | > 时间复杂度:最好O(log M n),最差O(log n)
187 |
188 | #### 命令描述
189 | > 判断指定的点、线或面是否包含在目标area的多边形中,若包含,则返回目标area中命中的多边形数量与多边形信息。
190 |
191 | #### 参数描述
192 | > area:一个几何概念。
193 | > polygonWkt:指定与目标area进行比较的多边形描述信息,使用WKT(Well-known text)描述,支持如下类型。
194 | > - POINT:描述一个点的WKT信息。
195 | > - LINESTRING:描述一条线的WKT信息。
196 | > - POLYGON:描述一个多边形的WKT信息。
197 | >
198 | > WITHOUTWKT:用于控制是否返回多边形的WKT信息,如果加上该参数,则不返回多边形的WKT信息。
199 |
200 | #### 返回值
201 | > 执行成功:命中的多边形数量与多边形信息。
202 | > area不存在:empty list or set。
203 | > 其它情况返回相应的异常信息。
204 |
205 | #### 示例
206 | ```
207 | 提前执行 GIS.ADD hangzhou campus 'POLYGON ((30 10, 40 40, 20 40, 10 20, 30 10))'命令
208 |
209 | 127.0.0.1:6379> GIS.CONTAINS hangzhou 'POINT (30 11)'
210 | 1) (integer) 1
211 | 2) 1) "campus"
212 | 2) "POLYGON((30 10,40 40,20 40,10 20,30 10))"
213 | 127.0.0.1:6379>
214 | ```
215 |
216 | ### GIS.WITHIN
217 | #### 语法及复杂度
218 | > GIS.WITHIN area polygonWkt [WITHOUTWKT]
219 | > 时间复杂度:最好O(log M n),最差O(log n)
220 |
221 | #### 命令描述
222 | > 判断目标area是否包含在指定的点、线或面中,若包含,则返回目标area中命中的多边形数量与多边形信息。
223 |
224 | #### 参数描述
225 | > area:一个几何概念。
226 | > polygonWkt:指定与目标area进行比较的多边形描述信息,使用WKT(Well-known text)描述,支持如下类型。
227 | > - POINT:描述一个点的WKT信息。
228 | > - LINESTRING:描述一条线的WKT信息。
229 | > - POLYGON:描述一个多边形的WKT信息。
230 | >
231 | > WITHOUTWKT:用于控制是否返回多边形的WKT信息,如果加上该参数,则不返回多边形的WKT信息。
232 |
233 | #### 返回值
234 | > 执行成功:命中的多边形数量与多边形信息。
235 | > area不存在:empty list or set。
236 | > 其它情况返回相应的异常信息。
237 |
238 | #### 示例
239 | ```
240 | 提前执行 GIS.ADD hangzhou campus 'POLYGON ((30 10, 40 40, 20 40, 10 20, 30 10))'命令
241 |
242 | 127.0.0.1:6379> GIS.WITHIN hangzhou 'POLYGON ((30 5, 50 50, 20 50, 5 20, 30 5))'
243 | 1) (integer) 1
244 | 2) 1) "campus"
245 | 2) "POLYGON((30 10,40 40,20 40,10 20,30 10))"
246 | 127.0.0.1:6379>
247 | ```
248 |
249 | ### GIS.INTERSECTS
250 | #### 语法及复杂度
251 | > GIS.INTERSECTS area polygonWkt [WITHOUTWKT]
252 | > 时间复杂度:最好O(log M n),最差O(log n)
253 |
254 | #### 命令描述
255 | > 判断指定的点、线或面与目标area的多边形是否相交,若相交,则返回目标area中与其相交的多边形数量与多边形信息。
256 |
257 | #### 参数描述
258 | > area:一个几何概念。
259 | > polygonWkt:指定与目标area进行比较的多边形描述信息,使用WKT(Well-known text)描述,支持如下类型。
260 | > - POINT:描述一个点的WKT信息。
261 | > - LINESTRING:描述一条线的WKT信息。
262 | > - POLYGON:描述一个多边形的WKT信息。
263 | >
264 | > WITHOUTWKT:用于控制是否返回多边形的WKT信息,如果加上该参数,则不返回多边形的WKT信息。
265 |
266 | #### 返回值
267 | > 执行成功:命中的多边形数量与多边形信息。
268 | > area不存在:empty list or set。
269 | > 其它情况返回相应的异常信息。
270 |
271 | #### 示例
272 | ```
273 | 提前执行 GIS.ADD hangzhou campus 'POLYGON ((30 10, 40 40, 20 40, 10 20, 30 10))'命令
274 |
275 | 127.0.0.1:6379> GIS.INTERSECTS hangzhou 'LINESTRING (30 10, 40 40)'
276 | 1) (integer) 1
277 | 2) 1) "campus"
278 | 2) "POLYGON((30 10,40 40,20 40,10 20,30 10))"
279 | 127.0.0.1:6379>
280 | ```
281 |
282 | ### GIS.SEARCH
283 | #### 语法及复杂度
284 | > GIS.SEARCH area [RADIUS longitude latitude distance m|km|ft|mi]
285 | > [MEMBER field distance m|km|ft|mi]
286 | > [GEOM geom]
287 | > [COUNT count]
288 | > [LIMIT limit]
289 | > [ASC|DESC]
290 | > [WITHDIST]
291 | > [WITHOUTWKT]
292 | > 时间复杂度:最好O(log M n),最差O(log n)
293 |
294 | #### 命令描述
295 | > 在指定经、纬度及半径距离范围内,查找目标area中的点。
296 |
297 | #### 参数描述
298 | > area:一个几何概念。
299 | > RADIUS:传入经度(longitude)、纬度(latitude)、半径距离(distance)和半径单位(m表示米、km表示千米、ft表示英尺、mi表示英里)进行搜索,例如RADIUS 15 37 200 km。
300 | > MEMBER:选择当前area中已存在的POINT作为搜索原点,并指定半径进行搜索,取值顺序为多边形名称(field)、半径(distance)、半径单位(m表示米、km表示千米、ft表示英尺、mi表示英里),例如MEMBER Agrigento 100 km。
301 | > GEOM:按照WKT的格式设置搜索范围,可以是任意多边形,例如GEOM 'POLYGON((10 30,20 30,20 40,10 40))'。
302 | > COUNT:用于限定返回的个数,例如COUNT 3。
303 | > LIMIT:Limit 与 Count 的区别是 Limit 是在搜索过程完成,只要搜索到 limit 个元素,就停止搜索(并不一定是最近的范围);但 Count 是搜索完所有元素并排序之后再进行过滤。
304 | > ASC|DESC:用于控制返回信息按照距离排序,ASC表示根据中心位置,由近到远排序;DESC表示由远到近排序。
305 | > WITHDIST:用于控制是否返回目标点与搜索原点的距离。
306 | > WITHOUTWKT:用于控制是否返回多边形的WKT信息,如果加上该参数,则不返回多边形的WKT信息。
307 | >
308 | > 说明:只能同时使用RADIUS、MEMBER和GEOM中的一种方式。
309 |
310 | #### 返回值
311 | > 执行成功:命中的目标点数量与WKT信息。
312 | > area不存在:empty list or set。
313 | > 其它情况返回相应的异常信息。
314 |
315 | #### 示例
316 | ```
317 | 提前执行GIS.ADD Sicily "Palermo" "POINT (13.361389 38.115556)" "Catania" "POINT(15.087269 37.502669)"命令。
318 |
319 | 127.0.0.1:6379> GIS.SEARCH Sicily RADIUS 15 37 200 km WITHDIST ASC
320 | 1) (integer) 2
321 | 2) 1) "Catania"
322 | 2) "POINT(15.087269 37.502669)"
323 | 3) "56.4413"
324 | 4) "Palermo"
325 | 5) "POINT(13.361389 38.115556)"
326 | 6) "190.4424"
327 | 127.0.0.1:6379>
328 | ```
329 |
330 | ## Tair Modules
331 | [TairHash](https://github.com/alibaba/TairHash): 和redis hash类似,但是可以为field设置expire和version,支持高效的主动过期和被动过期。
332 | [TairZset](https://github.com/alibaba/TairZset): 和redis zset类似,但是支持多(最大255)维排序,同时支持incrby语义,非常适合游戏排行榜场景。
333 | [TairString](https://github.com/alibaba/TairString): 和redis string类似,但是支持设置expire和version,并提供CAS/CAD等实用命令,非常适用于分布式锁等场景。
334 | [TairGis](https://github.com/alibaba/TairGis): 一个支持点、线、面之间相交、包含、被包含关系判断的Redis Module。
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |

3 |
4 |
5 | ## Introduction [中文介绍](README-CN.md)
6 | TairGis is a Redis Module that supports the query of `intersection`, `contains`, and `within` relationships between `points`, `lines`, and `polygons`. It has the following features:
7 | - Fast query performance
8 | - Use `RTree` storage
9 | - The function of Redis `GEORADIUS` [command](https://redis.io/commands/georadius) can be realized through `GIS.SEARCH`
10 |
11 | ## Acknowledgments
12 | Special thanks to https://github.com/tidwall/redis-gis, TairGis relies on redis-gis and fixes some of its problems.
13 |
14 | ## Quick Start
15 | ```
16 | // Insert a POLYGON
17 | 127.0.0.1:6379> GIS.ADD hangzhou campus 'POLYGON ((120.028041 30.285179, 120.031203 30.28691, 120.037311 30.286239, 120.034185 30.280844))'
18 | (integer) 1
19 | // Query POLYGON
20 | 127.0.0.1:6379> GIS.GET hangzhou campus
21 | "POLYGON((120.028041 30.285179,120.031203 30.28691,120.037311 30.286239,120.034185 30.280844))"
22 | // Determine if the points 'POINT (120.031939 30.285179)' and POLYGON contain
23 | 127.0.0.1:6379> GIS.CONTAINS hangzhou 'POINT (120.031939 30.285179)'
24 | 1) (integer) 1
25 | 2) 1) "campus"
26 | 2) "POLYGON((120.028041 30.285179,120.031203 30.28691,120.037311 30.286239,120.034185 30.280844))"
27 | // Determine if line and POLYGON intersect
28 | 127.0.0.1:6379> GIS.INTERSECTS hangzhou 'LINESTRING (120.029622 30.288952, 120.035488 30.280056)'
29 | 1) (integer) 1
30 | 2) 1) "campus"
31 | 2) "POLYGON((120.028041 30.285179,120.031203 30.28691,120.037311 30.286239,120.034185 30.280844))"
32 | 127.0.0.1:6379>
33 | ```
34 |
35 | ## BUILD
36 | ```
37 | make
38 | ```
39 | After the compilation is successful, the `tairgis.so` file will be generated in the current directory, next, load it with Redis.
40 | ```
41 | ./redis-server --loadmodule /path/to/tairgis.so
42 | ```
43 |
44 | ## Test
45 | Edit tests/tairgis.tcl first line: `set testmodule [file your_path/tairgis.so]`
46 |
47 | Copy tairgis.tcl in the tests directory to tests in the redis directory
48 | ```
49 | cp tests/tairgis.tcl your_redis_path/tests
50 | ```
51 | Add tairgis to all_tests in test_helper.tcl of redis
52 | ```
53 | diff --git a/tests/test_helper.tcl b/tests/test_helper.tcl
54 | index 1a5096937..d5a1ba40a 100644
55 | --- a/tests/test_helper.tcl
56 | +++ b/tests/test_helper.tcl
57 | @@ -13,6 +13,7 @@ source tests/support/test.tcl
58 | source tests/support/util.tcl
59 |
60 | set ::all_tests {
61 | + tairgis
62 | ```
63 | Run ./runtest --single tairgis in the redis root directory
64 |
65 | ## Clients
66 |
67 | | language | GitHub |
68 | |----------|---|
69 | | Java |https://github.com/alibaba/alibabacloud-tairjedis-sdk|
70 | | Python |https://github.com/alibaba/tair-py|
71 | | Go |https://github.com/alibaba/tair-go|
72 | | .Net |https://github.com/alibaba/AlibabaCloud.TairSDK|
73 |
74 | ## API
75 |
76 | ### GIS.ADD
77 | #### Syntax and Complexity
78 | > GIS.ADD area polygonName polygonWkt [polygonName polygonWkt ...]
79 | > Time complexity: O(log n)
80 |
81 | #### Command description
82 | > Add polygons with the specified name (multiple ones can be added) in the area, and use WKT (Well-known text) description. WKT is a text markup language for describing vector geometric objects,
83 | > Spatial reference system and conversion between spatial reference systems.
84 |
85 | #### Parameter Description
86 | > area: a geometric concept.
87 | > polygonName: The name of the polygon.
88 | > polygonWkt: The description information of the polygon, which represents the longitude and latitude of the real world. It is described by WKT (Well-known text) and supports the following types.
89 | > - POINT: Describes the WKT information of a point, such as 'POINT (120.086631 30.138141)', which means that the POINT is located at 120.086631 longitude and 30.138141 latitude.
90 | > - LINESTRING: WKT information describing a line, consisting of two POINTs, such as 'LINESTRING (30 10, 40 40)'.
91 | > - POLYGON: Describes the WKT information of a polygon, consisting of multiple POINTs, such as 'POLYGON ((31 20, 29 20, 29 21, 31 31))'.
92 | >
93 | > Description: The value range of longitude is (-180,180), and the value range of latitude is (-90,90). The following collection types are not supported: MULTIPOINT, MULTILINESTRING, MULTIPOLYGON, GEOMETRY, and COLLECTION.
94 |
95 | #### Return value
96 | > Executed successfully: Returns the number of polygons inserted and updated successfully.
97 | > In other cases, return the corresponding exception information.
98 |
99 | #### Example
100 | ````
101 | 127.0.0.1:6379> GIS.ADD hangzhou campus 'POLYGON ((30 10, 40 40, 20 40, 10 20, 30 10))'
102 | (integer) 1
103 | 127.0.0.1:6379>
104 | ````
105 |
106 | ### GIS.GET
107 | #### Syntax and Complexity
108 | > GIS.ADD area polygonName
109 | > Time complexity: O(1)
110 |
111 | #### Command description
112 | > Get the WKT information of the specified polygon in the target area.
113 |
114 | #### Parameter Description
115 | > area: a geometric concept.
116 | > polygonName: The name of the polygon.
117 |
118 | #### Return value
119 | > Successful execution: WKT information.
120 | > area or polygonName does not exist: nil.
121 | > In other cases, return the corresponding exception information.
122 |
123 | #### Example
124 | ````
125 | Execute the GIS.ADD hangzhou campus 'POLYGON ((30 10, 40 40, 20 40, 10 20, 30 10))' command in advance
126 |
127 | 127.0.0.1:6379> GIS.GET hangzhou campus
128 | "POLYGON((30 10,40 40,20 40,10 20,30 10))"
129 | 127.0.0.1:6379>
130 | ````
131 |
132 | ### GIS.GETALL
133 | #### Syntax and Complexity
134 | > GIS.ADD area [WITHOUTWKT]
135 | > Time complexity: O(n)
136 |
137 | #### Command description
138 | > Get the names and WKT information of all polygons in the target area. If the WITHOUTWKT option is set, only the name of the polygon is returned.
139 |
140 | #### Parameter Description
141 | > area: a geometric concept.
142 | > WITHOUTWKT: It is used to control whether to return the WKT information of the polygon. If this parameter is added, the WKT information of the polygon will not be returned.
143 |
144 | #### Return value
145 | > Successful execution: Returns the polygon name and WKT information. If the WITHOUTWKT option is set, only the polygon name is returned.
146 | > area does not exist: nil.
147 | > In other cases, return the corresponding exception information.
148 |
149 | #### Example
150 | ````
151 | Execute the GIS.ADD hangzhou campus 'POLYGON ((30 10, 40 40, 20 40, 10 20, 30 10))' command in advance
152 |
153 | 127.0.0.1:6379>GIS.GETALL hangzhou
154 | 1) "campus"
155 | 2) "POLYGON((30 10,40 40,20 40,10 20,30 10))"
156 | 127.0.0.1:6379>
157 | ````
158 |
159 | ### GIS.DEL
160 | #### Syntax and Complexity
161 | > GIS.DEL area polygonName
162 | > Time complexity: O(log n)
163 |
164 | #### Command description
165 | > Delete the specified polygons in the target area.
166 |
167 | #### Parameter Description
168 | > area: a geometric concept.
169 | > polygonName: The name of the polygon.
170 |
171 | #### return value
172 | > Successful execution: OK.
173 | > area or polygonName does not exist: nil.
174 | > In other cases, return the corresponding exception information.
175 |
176 | #### Example
177 | ````
178 | Execute the GIS.ADD hangzhou campus 'POLYGON ((30 10, 40 40, 20 40, 10 20, 30 10))' command in advance
179 |
180 | 127.0.0.1:6379> GIS.DEL hangzhou campus
181 | OK
182 | 127.0.0.1:6379>
183 | ````
184 |
185 | ### GIS.CONTAINS
186 | #### Syntax and Complexity
187 | > GIS.CONTAINS area polygonWkt [WITHOUTWKT]
188 | > Time complexity: O(log M n) at best, O(log n) at worst
189 |
190 | #### Command description
191 | > Determine whether the specified point, line or surface is included in the polygon of the target area. If so, return the number of polygons and polygon information hit in the target area.
192 |
193 | #### Parameter Description
194 | > area: a geometric concept.
195 | > polygonWkt: Specifies the polygon description information to be compared with the target area, using WKT (Well-known text) description, supports the following types.
196 | > - POINT: WKT information describing a point.
197 | > - LINESTRING: WKT information describing a line.
198 | > - POLYGON: WKT information describing a polygon.
199 | >
200 | > WITHOUTWKT: It is used to control whether to return the WKT information of the polygon. If this parameter is added, the WKT information of the polygon will not be returned.
201 |
202 | #### return value
203 | > Successful execution: number of hit polygons and polygon information.
204 | > area does not exist: empty list or set.
205 | > In other cases, return the corresponding exception information.
206 |
207 | #### Example
208 | ````
209 | Execute the GIS.ADD hangzhou campus 'POLYGON ((30 10, 40 40, 20 40, 10 20, 30 10))' command in advance
210 |
211 | 127.0.0.1:6379> GIS.CONTAINS hangzhou 'POINT (30 11)'
212 | 1) (integer) 1
213 | 2) 1) "campus"
214 | 2) "POLYGON((30 10,40 40,20 40,10 20,30 10))"
215 | 127.0.0.1:6379>
216 | ````
217 |
218 | ### GIS.WITHIN
219 | #### Syntax and Complexity
220 | > GIS.WITHIN area polygonWkt [WITHOUTWKT]
221 | > Time complexity: O(log M n) at best, O(log n) at worst
222 |
223 | #### Command description
224 | > Determine whether the target area is contained in the specified point, line or surface. If so, return the number of polygons and polygon information hit in the target area.
225 |
226 | #### Parameter Description
227 | > area: a geometric concept.
228 | > polygonWkt: Specifies the polygon description information to be compared with the target area, using WKT (Well-known text) description, supports the following types.
229 | > - POINT: WKT information describing a point.
230 | > - LINESTRING: WKT information describing a line.
231 | > - POLYGON: WKT information describing a polygon.
232 | >
233 | > WITHOUTWKT: It is used to control whether to return the WKT information of the polygon. If this parameter is added, the WKT information of the polygon will not be returned.
234 |
235 | #### return value
236 | > Successful execution: number of hit polygons and polygon information.
237 | > area does not exist: empty list or set.
238 | > In other cases, return the corresponding exception information.
239 |
240 | #### Example
241 | ````
242 | Execute the GIS.ADD hangzhou campus 'POLYGON ((30 10, 40 40, 20 40, 10 20, 30 10))' command in advance
243 |
244 | 127.0.0.1:6379> GIS.WITHIN hangzhou 'POLYGON ((30 5, 50 50, 20 50, 5 20, 30 5))'
245 | 1) (integer) 1
246 | 2) 1) "campus"
247 | 2) "POLYGON((30 10,40 40,20 40,10 20,30 10))"
248 | 127.0.0.1:6379>
249 | ````
250 |
251 | ### GIS.INTERSECTS
252 | #### Syntax and Complexity
253 | > GIS.INTERSECTS area polygonWkt [WITHOUTWKT]
254 | > Time complexity: O(log M n) at best, O(log n) at worst
255 |
256 | #### Command description
257 | > Determine whether the specified point, line or surface intersects with the polygon of the target area. If so, return the number of polygons and polygon information in the target area that intersect with it.
258 |
259 | #### Parameter Description
260 | > area: a geometric concept.
261 | > polygonWkt: Specifies the polygon description information to be compared with the target area, using WKT (Well-known text) description, supports the following types.
262 | > - POINT: WKT information describing a point.
263 | > - LINESTRING: WKT information describing a line.
264 | > - POLYGON: WKT information describing a polygon.
265 | >
266 | > WITHOUTWKT: It is used to control whether to return the WKT information of the polygon. If this parameter is added, the WKT information of the polygon will not be returned.
267 |
268 | #### return value
269 | > Successful execution: number of hit polygons and polygon information.
270 | > area does not exist: empty list or set.
271 | > In other cases, return the corresponding exception information.
272 |
273 | #### Example
274 | ````
275 | Execute the GIS.ADD hangzhou campus 'POLYGON ((30 10, 40 40, 20 40, 10 20, 30 10))' command in advance
276 |
277 | 127.0.0.1:6379> GIS.INTERSECTS hangzhou 'LINESTRING (30 10, 40 40)'
278 | 1) (integer) 1
279 | 2) 1) "campus"
280 | 2) "POLYGON((30 10,40 40,20 40,10 20,30 10))"
281 | 127.0.0.1:6379>
282 | ````
283 |
284 | ### GIS.SEARCH
285 | #### Syntax and Complexity
286 | > GIS.SEARCH area [RADIUS longitude latitude distance m|km|ft|mi]
287 | > [MEMBER field distance m|km|ft|mi]
288 | > [GEOM geom]
289 | > [COUNT count]
290 | > [LIMIT limit]
291 | > [ASC|DESC]
292 | > [WITHDIST]
293 | > [WITHOUTWKT]
294 | > Time complexity: O(log M n) at best, O(log n) at worst
295 |
296 | #### Command description
297 | > Find the point in the target area within the specified longitude, latitude and radius distance.
298 |
299 | #### Parameter Description
300 | > area: a geometric concept.
301 | > RADIUS: Enter longitude, latitude, distance and radius units (m for meters, km for kilometers, ft for feet, mi for miles) to search, for example RADIUS 15 37 200 km .
302 | > MEMBER: Select the existing POINT in the current area as the search origin, and specify the radius to search. The value order is polygon name (field), radius (distance), radius unit (m means meter, km means kilometer, ft means feet, mi for miles), such as MEMBER Agrigento 100 km.
303 | > GEOM: Set the search range according to the WKT format, which can be any polygon, such as GEOM 'POLYGON((10 30,20 30,20 40,10 40))'.
304 | > COUNT: Used to limit the number of returned items, such as COUNT 3.
305 | > LIMIT: The difference between Limit and Count is: Limit is completed during the search process, as long as limit elements are searched, the search will stop; but Count is filtering after searching all elements.
306 | > ASC|DESC: Used to control the return information to be sorted by distance. ASC means sorting from near to far according to the center position; DESC means sorting from far to near.
307 | > WITHDIST: Used to control whether to return the distance between the target point and the search origin.
308 | > WITHOUTWKT: It is used to control whether to return the WKT information of the polygon. If this parameter is added, the WKT information of the polygon will not be returned.
309 | >
310 | > Note: Only one of RADIUS, MEMBER and GEOM can be used at the same time.
311 |
312 | #### Return value
313 | > Successful execution: the number of target points hit and WKT information.
314 | > area does not exist: empty list or set.
315 | > In other cases, return the corresponding exception information.
316 |
317 | #### Example
318 | ````
319 | Execute the GIS.ADD Sicily "Palermo" "POINT (13.361389 38.115556)" "Catania" "POINT(15.087269 37.502669)" command in advance.
320 |
321 | 127.0.0.1:6379> GIS.SEARCH Sicily RADIUS 15 37 200 km WITHDIST ASC
322 | 1) (integer) 2
323 | 2) 1) "Catania"
324 | 2) "POINT(15.087269 37.502669)"
325 | 3) "56.4413"
326 | 4) "Palermo"
327 | 5) "POINT(13.361389 38.115556)"
328 | 6) "190.4424"
329 | 127.0.0.1:6379>
330 | ````
331 |
332 | ## Tair Modules
333 | [TairHash](https://github.com/alibaba/TairHash): A redis module, similar to redis hash, but you can set expire and version for the field.
334 | [TairZset](https://github.com/alibaba/TairZset): A redis module, similar to redis zset, but you can set multiple scores for each member to support multi-dimensional sorting.
335 | [TairString](https://github.com/alibaba/TairString): A redis module, similar to redis string, but you can set expire and version for the value. It also provides many very useful commands, such as cas/cad, etc.
336 | [TairGis](https://github.com/alibaba/TairGis): A redis module that supports the query of `intersection`, `contains`, and `within` relationships between `points`, `lines`, and `polygons`.
--------------------------------------------------------------------------------
/imgs/tairgis_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tair-opensource/TairGis/16b1dba038c7bac59b52fc823ab7d368a96bdb3a/imgs/tairgis_logo.png
--------------------------------------------------------------------------------
/src/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | set(SOURCE_FILES
2 | tairgis.c
3 | spatial.c
4 | util.c
5 | spatial/geom.c
6 | spatial/grisu3.c
7 | spatial/rtree.c
8 | spatial/geoutil.c
9 | spatial/poly.c
10 | spatial/polyinside.c
11 | spatial/polyintersects.c
12 | spatial/polyraycast.c
13 | spatial/hash.c
14 | spatial/bing.c
15 | spatial/json.c)
16 |
17 | set(CMAKE_MACOSX_RPATH 1)
18 | add_library(tairgis SHARED ${SOURCE_FILES})
--------------------------------------------------------------------------------
/src/spatial.c:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023 Alibaba Tair Team
3 | * Copyright (c) 2016, Josh Baker
4 | * All rights reserved.
5 | *
6 | * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
7 | * following conditions are met:
8 | *
9 | * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
10 | * disclaimer.
11 | *
12 | * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
13 | * following disclaimer in the documentation and/or other materials provided with the distribution.
14 | *
15 | * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote
16 | * products derived from this software without specific prior written permission.
17 | *
18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
19 | * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
22 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23 | * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 | */
26 |
27 | #include
28 | #include
29 | #include
30 | #include
31 | #include "spatial.h"
32 | #include "util.h"
33 | #include "tairgis.h"
34 |
35 | int matchSearch(
36 | geom g, geomPolyMap *targetMap,
37 | int targetType, int searchType,
38 | geomCoord center, double meters
39 | );
40 |
41 | spatial *spatialNew() {
42 | spatial *s = RedisModule_Alloc(sizeof(spatial));
43 | if (!s) return NULL;
44 | s->h = RedisModule_CreateDict(NULL);
45 | s->keyhash = RedisModule_CreateDict(NULL);
46 | s->idxhash = RedisModule_CreateDict(NULL);
47 | s->tr = rtreeNew();
48 | s->fences = NULL;
49 | if (!s->tr) {
50 | spatialFree(s);
51 | return NULL;
52 | }
53 | return s;
54 | }
55 |
56 | void redisModuleDictFree(RedisModuleDict *h) {
57 | if (!h) return;
58 | size_t keylen;
59 | void *data;
60 | RedisModuleDictIter *iter = RedisModule_DictIteratorStartC(
61 | h, "^", NULL, 0);
62 | while (RedisModule_DictNextC(iter, &keylen, &data)) {
63 | GisModule_FreeStringSafe(NULL, (RedisModuleString *) data);
64 | }
65 | RedisModule_FreeDict(NULL, h);
66 | RedisModule_DictIteratorStop(iter);
67 | }
68 |
69 | void spatialFree(spatial *s) {
70 | if (s) {
71 | if (s->h) redisModuleDictFree(s->h);
72 | if (s->keyhash) redisModuleDictFree(s->keyhash);
73 | if (s->idxhash) redisModuleDictFree(s->idxhash);
74 | if (s->tr) rtreeFree(s->tr);
75 | /* do not free the fence object, only the array.
76 | * seems there exists some mem leak */
77 | if (s->fences) RedisModule_Free(s->fences);
78 | RedisModule_Free(s);
79 | }
80 | }
81 |
82 | // get an sds based on the key.
83 | // return value must be freed by the caller.
84 | static RedisModuleString * hashTypeGetRedisModuleString(RedisModuleDict *o, RedisModuleString *key) {
85 | int nokey = 0;
86 | RedisModuleString *vstr = RedisModule_DictGet(o, key, &nokey);
87 | if (nokey == 1 || !vstr) return NULL;
88 | return vstr;
89 | }
90 |
91 | // returns a raw pointer to the value
92 | static const void *hashTypeGetRaw(RedisModuleDict *o, RedisModuleString *key) {
93 | int nokey = 0;
94 | RedisModuleString *vstr = RedisModule_DictGet(o, key, &nokey);
95 | if (nokey == 1 || !vstr) return NULL;
96 | return RedisModule_StringPtrLen(vstr, NULL);
97 | }
98 |
99 | int spatialTypeSet(ExGisObj *o, RedisModuleString *field, RedisModuleString *val) {
100 | geom g;
101 | geomRect r;
102 | RedisModuleString *sidx;
103 | uint64_t nidx;
104 | spatial *s = o->s;
105 |
106 | g = (geom) RedisModule_StringPtrLen(val, NULL);
107 | r = geomBounds(g);
108 |
109 | /* try to delete the former existing data for 'field'
110 | * also remove what's exiting in rtree */
111 | spatialTypeDelete(o, field, &r, NULL);
112 |
113 | /* create a new idx / field entry */
114 | s->idx++;
115 | nidx = (uint64_t) s->idx;
116 | sidx = RedisModule_CreateString(NULL, (const char *) &nidx, 8);
117 |
118 | GisModule_DictInsertOrUpdate(s->idxhash, sidx, field);
119 | GisModule_DictInsertOrUpdate(s->keyhash, field, sidx);
120 | GisModule_FreeStringSafe(NULL, sidx);
121 |
122 | GisModule_DictInsertOrUpdate(s->h, field, val);
123 |
124 | /* update the rtree */
125 | rtreeInsert(s->tr, r.min.x, r.min.y, r.max.x, r.max.y, s->idx);
126 |
127 | return 1;
128 | }
129 |
130 | RedisModuleString *decodeOrReply(RedisModuleCtx *ctx, const char *value) {
131 | geom g = NULL;
132 | int sz = 0;
133 | geomErr err = geomDecode(value, strlen(value), 0, &g, &sz);
134 | if (err != GEOM_ERR_NONE) {
135 | RedisModule_ReplyWithError(ctx, "ERR invalid geometry");
136 | return NULL;
137 | }
138 | RedisModuleString *gvalue = RedisModule_CreateString(NULL, g, (size_t) sz);
139 | geomFree(g);
140 | return gvalue;
141 | }
142 |
143 | int matchSearch(
144 | geom g, geomPolyMap *targetMap,
145 | int targetType, int searchType,
146 | geomCoord center, double meters
147 | ) {
148 | int match = 0;
149 | if (geomIsSimplePoint(g) && targetType == RADIUS) {
150 | match = geomCoordWithinRadius(geomCenter(g), center, meters);
151 | } else {
152 | geomPolyMap *m = geomNewPolyMap(g);
153 | if (!m) {
154 | return 0;
155 | }
156 | if (searchType == WITHIN) {
157 | match = geomPolyMapWithin(m, targetMap);
158 | } else if (searchType == INTERSECTS) {
159 | match = geomPolyMapIntersects(m, targetMap);
160 | } else if(searchType == EX_CONTAINS) {
161 | match = geomPolyMapContains(m,targetMap);
162 | } else if(searchType == EX_INTERSECTS) {
163 | match = geomPolyMapExIntersects(m,targetMap);
164 | }
165 | geomFreePolyMap(m);
166 | }
167 | return match;
168 | }
169 |
170 | int RedisModule_StringPtrIsEmpty(const char *str) {
171 | return strcmp(str, "(NULL string reply referenced in module)");
172 | }
173 |
174 | int spatialTypeDelete(ExGisObj *o, RedisModuleString *field, geomRect *rin, int *isEmpty) {
175 | geomRect r;
176 | RedisModuleString *sidx;
177 | char *idx;
178 | int res;
179 | spatial *s = o->s;
180 | const char *cstr = NULL;
181 | size_t len = 0;
182 |
183 | /* get the idx */
184 | sidx = hashTypeGetRedisModuleString(s->keyhash, field);
185 | cstr = RedisModule_StringPtrLen(sidx, &len);
186 | if (!RedisModule_StringPtrIsEmpty(cstr) || len != 8) return 0;
187 | idx = (char *) (*((uint64_t *) cstr));
188 |
189 | geom g = NULL;
190 | if (rin) {
191 | r = *rin;
192 | } else {
193 | const void *raw = hashTypeGetRaw(s->h, field);
194 | if (!raw) return 0;
195 | g = (geom) raw;
196 | r = geomBounds(g);
197 | }
198 |
199 | rtreeRemove(s->tr, r.min.x, r.min.y, r.max.x, r.max.y, idx);
200 |
201 | RedisModuleString *o1 = NULL, *o2 = NULL, *o3 = NULL;
202 | res = RedisModule_DictDel(s->h, field, &o1) || RedisModule_DictDel(s->idxhash, sidx, &o2) ||
203 | RedisModule_DictDel(s->keyhash, field, &o3);
204 |
205 | GisModule_FreeStringSafe(NULL, o1);
206 | GisModule_FreeStringSafe(NULL, o2);
207 | GisModule_FreeStringSafe(NULL, o3);
208 |
209 | if (RedisModule_DictSize(s->keyhash) == 0 && isEmpty) {
210 | *isEmpty = 1;
211 | }
212 |
213 | return res == REDISMODULE_OK ? 1 : 0;
214 | }
215 |
216 | /* Importing some stuff from t_hash.c but these should exist in server.h */
217 | void addGeomReplyBulkCBuffer(RedisModuleCtx *ctx, const void *p) {
218 | char *wkt = geomEncodeWKT((geom) p, 0);
219 | if (!wkt) {
220 | RedisModule_ReplyWithError(ctx, "ERR failed to encode wkt");
221 | return;
222 | }
223 | RedisModule_ReplyWithStringBuffer(ctx, wkt, strlen(wkt));
224 | geomFreeWKT(wkt);
225 | }
226 |
227 | /* These are direct copies from t_hash.c because they're defined as static and
228 | * I didn't want to change the source file. */
229 | void addGeomHashFieldToReply(RedisModuleCtx *ctx, ExGisObj *o, RedisModuleString *field) {
230 | if (o == NULL) {
231 | RedisModule_ReplyWithNull(ctx);
232 | return;
233 | }
234 | RedisModuleString *vstr = NULL;
235 | int nokey = 0;
236 |
237 | vstr = RedisModule_DictGet(o->s->h, field, &nokey);
238 | if (nokey == 1 || !vstr) {
239 | RedisModule_ReplyWithNull(ctx);
240 | return;
241 | }
242 |
243 | addGeomReplyBulkCBuffer(ctx, RedisModule_StringPtrLen(vstr, NULL));
244 | }
245 |
246 | void addGeomHashAllToReply(RedisModuleCtx *ctx, ExGisObj *o, int flag) {
247 | if (o == NULL) {
248 | RedisModule_ReplyWithNull(ctx);
249 | return;
250 | }
251 |
252 | long size = (long) RedisModule_DictSize(o->s->h);
253 | RedisModule_ReplyWithArray(ctx, flag & GIS_WITHVALUE ? size * 2 : size);
254 |
255 | void *field, *val;
256 | RedisModuleDictIter *iter = RedisModule_DictIteratorStartC(
257 | o->s->h, "^", NULL, 0);
258 | while ((field = RedisModule_DictNext(NULL, iter, &val)) != NULL) {
259 | RedisModule_ReplyWithString(ctx, field);
260 | if (flag & GIS_WITHVALUE) {
261 | addGeomReplyBulkCBuffer(ctx, RedisModule_StringPtrLen(val, NULL));
262 | }
263 | GisModule_FreeStringSafe(NULL, field);
264 | }
265 | RedisModule_DictIteratorStop(iter);
266 | }
267 |
268 | /* Glob-style pattern matching. */
269 | // Move from redis src/util.c
270 | static int stringmatchlen(const char *pattern, int patternLen,
271 | const char *string, int stringLen, int nocase)
272 | {
273 | while(patternLen && stringLen) {
274 | switch(pattern[0]) {
275 | case '*':
276 | while (pattern[1] == '*') {
277 | pattern++;
278 | patternLen--;
279 | }
280 | if (patternLen == 1)
281 | return 1; /* match */
282 | while(stringLen) {
283 | if (stringmatchlen(pattern+1, patternLen-1,
284 | string, stringLen, nocase))
285 | return 1; /* match */
286 | string++;
287 | stringLen--;
288 | }
289 | return 0; /* no match */
290 | break;
291 | case '?':
292 | if (stringLen == 0)
293 | return 0; /* no match */
294 | string++;
295 | stringLen--;
296 | break;
297 | case '[':
298 | {
299 | int not, match;
300 |
301 | pattern++;
302 | patternLen--;
303 | not = pattern[0] == '^';
304 | if (not) {
305 | pattern++;
306 | patternLen--;
307 | }
308 | match = 0;
309 | while(1) {
310 | if (pattern[0] == '\\' && patternLen >= 2) {
311 | pattern++;
312 | patternLen--;
313 | if (pattern[0] == string[0])
314 | match = 1;
315 | } else if (pattern[0] == ']') {
316 | break;
317 | } else if (patternLen == 0) {
318 | pattern--;
319 | patternLen++;
320 | break;
321 | } else if (pattern[1] == '-' && patternLen >= 3) {
322 | int start = pattern[0];
323 | int end = pattern[2];
324 | int c = string[0];
325 | if (start > end) {
326 | int t = start;
327 | start = end;
328 | end = t;
329 | }
330 | if (nocase) {
331 | start = tolower(start);
332 | end = tolower(end);
333 | c = tolower(c);
334 | }
335 | pattern += 2;
336 | patternLen -= 2;
337 | if (c >= start && c <= end)
338 | match = 1;
339 | } else {
340 | if (!nocase) {
341 | if (pattern[0] == string[0])
342 | match = 1;
343 | } else {
344 | if (tolower((int)pattern[0]) == tolower((int)string[0]))
345 | match = 1;
346 | }
347 | }
348 | pattern++;
349 | patternLen--;
350 | }
351 | if (not)
352 | match = !match;
353 | if (!match)
354 | return 0; /* no match */
355 | string++;
356 | stringLen--;
357 | break;
358 | }
359 | case '\\':
360 | if (patternLen >= 2) {
361 | pattern++;
362 | patternLen--;
363 | }
364 | /* fall through */
365 | default:
366 | if (!nocase) {
367 | if (pattern[0] != string[0])
368 | return 0; /* no match */
369 | } else {
370 | if (tolower((int)pattern[0]) != tolower((int)string[0]))
371 | return 0; /* no match */
372 | }
373 | string++;
374 | stringLen--;
375 | break;
376 | }
377 | pattern++;
378 | patternLen--;
379 | if (stringLen == 0) {
380 | while(*pattern == '*') {
381 | pattern++;
382 | patternLen--;
383 | }
384 | break;
385 | }
386 | }
387 | if (patternLen == 0 && stringLen == 0)
388 | return 1;
389 | return 0;
390 | }
391 |
392 | int searchIterator(double minX, double minY, double maxX, double maxY, void *item, void *userdata) {
393 | (void)(minX);(void)(minY);(void)(maxX);(void)(maxY); // unused vars.
394 |
395 | searchContext *ctx = userdata;
396 |
397 | /* if limit reach, just return */
398 | if ((ctx->limit != 0) && (ctx->len >= ctx->limit)) {
399 | return 1;
400 | }
401 |
402 | /* retrieve the field */
403 | int nokey = 0;
404 | uint64_t nidx = (uint64_t)item;
405 | RedisModuleString *sidx = RedisModule_CreateString(NULL, (const char *) &nidx, 8);
406 | RedisModuleString *field = RedisModule_DictGet(ctx->s->idxhash, sidx, &nokey);
407 | GisModule_FreeStringSafe(NULL, sidx);
408 | if (nokey == 1) {
409 | return 1;
410 | }
411 |
412 | const char *fieldStr = NULL, *valueStr = NULL;
413 | size_t filedLen, valueLen;
414 | fieldStr = RedisModule_StringPtrLen(field, &filedLen);
415 |
416 | if (!(ctx->allfields ||
417 | stringmatchlen(ctx->pattern, (int) strlen(ctx->pattern), fieldStr, (int) filedLen, 0))) {
418 | return 1;
419 | }
420 |
421 | // retrieve the geom
422 | RedisModuleString *value = RedisModule_DictGet(ctx->s->h, field, &nokey);
423 | valueStr = RedisModule_StringPtrLen(value, &valueLen);
424 | if (nokey == 1){
425 | return 1;
426 | }
427 |
428 | geom g = (geom)valueStr;
429 |
430 | int match = matchSearch(g, ctx->m, ctx->targetType, ctx->searchType, ctx->center, ctx->meters);
431 | if (!match){
432 | return 1;
433 | }
434 | // append item
435 | if (ctx->len == ctx->cap) {
436 | int ncap = ctx->cap;
437 | if (ncap == 0){
438 | ncap = 1;
439 | } else {
440 | ncap *= 2;
441 | }
442 | resultItem *nresults = RedisModule_Realloc(ctx->results, ncap*sizeof(resultItem));
443 | if (!nresults){
444 | RedisModule_ReplyWithError(ctx->c, "ERR out of memory");
445 | ctx->fail = 1;
446 | return 0;
447 | }
448 | ctx->results = nresults;
449 | ctx->cap = ncap;
450 | }
451 |
452 | ctx->results[ctx->len].field = field;
453 | ctx->results[ctx->len].value = value;
454 | ctx->len++;
455 |
456 | return 1;
457 | }
458 |
459 | int sortDistanceAsc(const void *a, const void *b) {
460 | resultItem *da = (resultItem *)a, *db = (resultItem *)b;
461 | if (da->distance > db->distance) {
462 | return 1;
463 | } else if (fabs(da->distance - db->distance) < 0.0000001) {
464 | return 0;
465 | } else {
466 | return -1;
467 | }
468 | }
469 |
470 | int sortDistanceDesc(const void *a, const void *b) {
471 | return -sortDistanceAsc(a, b);
472 | }
473 |
--------------------------------------------------------------------------------
/src/spatial.h:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023 Alibaba Tair Team
3 | * Copyright (c) 2016, Josh Baker
4 | * All rights reserved.
5 | *
6 | * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
7 | * following conditions are met:
8 | *
9 | * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
10 | * disclaimer.
11 | *
12 | * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
13 | * following disclaimer in the documentation and/or other materials provided with the distribution.
14 | *
15 | * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote
16 | * products derived from this software without specific prior written permission.
17 | *
18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
19 | * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
22 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23 | * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 | */
26 |
27 | #ifndef SPATIAL_H
28 | #define SPATIAL_H
29 |
30 | #include "spatial/rtree.h"
31 | #include "spatial/geoutil.h"
32 | #include "spatial/geom.h"
33 | #include "spatial/hash.h"
34 | #include "spatial/bing.h"
35 | #include "redismodule.h"
36 |
37 | #define FENCE_ENTER (1<<1)
38 | #define FENCE_EXIT (1<<2)
39 | #define FENCE_CROSS (1<<3)
40 | #define FENCE_INSIDE (1<<4)
41 | #define FENCE_OUTSIDE (1<<5)
42 | #define FENCE_KEYDEL (1<<6)
43 | #define FENCE_FIELDDEL (1<<7)
44 | #define FENCE_FLUSH (1<<8)
45 | #define FENCE_ALL (FENCE_ENTER|FENCE_EXIT|FENCE_CROSS|\
46 | FENCE_INSIDE|FENCE_OUTSIDE|FENCE_KEYDEL|\
47 | FENCE_FIELDDEL|FENCE_FIELDDEL)
48 |
49 | #define FENCE_NOTIFY_SET 1
50 | #define FENCE_NOTIFY_DEL 2
51 |
52 | #define WITHIN 1
53 | #define INTERSECTS 2
54 | #define EX_CONTAINS 3
55 | #define EX_INTERSECTS 4
56 |
57 | #define RADIUS 1
58 | #define GEOMETRY 2
59 | #define BOUNDS 3
60 |
61 | #define OUTPUT_COUNT 1
62 | #define OUTPUT_FIELD 2
63 | #define OUTPUT_WKT 3
64 | #define OUTPUT_WKB 4
65 | #define OUTPUT_JSON 5
66 | #define OUTPUT_POINT 6
67 | #define OUTPUT_BOUNDS 7
68 | #define OUTPUT_HASH 8
69 | #define OUTPUT_QUAD 9
70 | #define OUTPUT_TILE 10
71 |
72 | typedef struct fence {
73 | //robj *channel;
74 | int allfields;
75 | char *pattern;
76 | int targetType;
77 | geomCoord center;
78 | double meters;
79 | int searchType;
80 | geom g;
81 | int sz;
82 | geomPolyMap *m;
83 | } fence;
84 |
85 | typedef struct spatial {
86 | RedisModuleDict *h; // main hash store that persists to RDB.
87 | rtree *tr; // underlying spatial index.
88 | fence **fences; // the stored fences
89 | int fcap, flen; // the cap/len for fence array
90 |
91 | // The following fields are for mapping an idx to a key and
92 | // vice versa. The rtree expects that each entry has a pointer to
93 | // an object in memory. This should be the base address of the
94 | // sds key that is stored in the main hash store, that way there is
95 | // no extra memory overhead except a pointer. But at the moment I'm
96 | // 100% sure how safe it is to expect that a key in the hash will
97 | // not change it's base pointer address. So until further testing
98 | // around hash key stability we will increment an idx pointer and
99 | // map this value to a key, then assign this idx value to each
100 | // rtree entry. This allows a reverse lookup to the key. This is a
101 | // safe (albeit slower and higher mem usage) way to track entries.
102 | char *idx; // pointer that acts as a private id for entries.
103 | RedisModuleDict *keyhash; // stores key -> idx
104 | RedisModuleDict *idxhash; // stores idx -> key
105 | } spatial;
106 |
107 | typedef struct resultItem {
108 | RedisModuleString *field;
109 | RedisModuleString *value;
110 | double distance;
111 | } resultItem;
112 |
113 | typedef struct searchContext {
114 | spatial *s;
115 | RedisModuleCtx *c;
116 | int searchType;
117 | int targetType;
118 | int fail;
119 | int len;
120 | int cap;
121 | resultItem *results;
122 | long long cursor;
123 | char *pattern;
124 | int allfields;
125 | int output;
126 | int precision;
127 | int nofields;
128 | int fence;
129 | int releaseg;
130 |
131 | // bounds
132 | geomRect bounds;
133 |
134 | // radius
135 | geomCoord center;
136 | double meters;
137 | double to_meters;
138 |
139 | // geometry
140 | geom g;
141 | int sz;
142 | geomPolyMap *m;
143 |
144 | // flags eg. WITHOUTWKT
145 | int flag;
146 | long long count;
147 | long long limit;
148 |
149 | } searchContext;
150 |
151 | typedef struct ExGisObj {
152 | spatial *s;
153 | } ExGisObj;
154 |
155 | spatial *spatialNew();
156 | void spatialFree(spatial *s);
157 | int spatialTypeSet(ExGisObj *o, RedisModuleString *field, RedisModuleString *val);
158 | int spatialTypeDelete(ExGisObj *o, RedisModuleString *field, geomRect *rin, int *isEmpty);
159 | RedisModuleString *decodeOrReply(RedisModuleCtx *ctx, const char *value);
160 | void addGeomHashFieldToReply(RedisModuleCtx *ctx, ExGisObj *o, RedisModuleString *field);
161 | void addGeomHashAllToReply(RedisModuleCtx *ctx, ExGisObj *o, int flag);
162 | int searchIterator(double minX, double minY, double maxX, double maxY, void *item, void *userdata);
163 | int sortDistanceAsc(const void *a, const void *b);
164 | int sortDistanceDesc(const void *a, const void *b);
165 |
166 | #endif // SPATIAL_H
167 |
168 |
--------------------------------------------------------------------------------
/src/spatial/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | *.dSYM
3 | test
4 | *.o
5 | a.out
--------------------------------------------------------------------------------
/src/spatial/Makefile:
--------------------------------------------------------------------------------
1 | STD=
2 | WARN= -Wall -Wno-strict-aliasing -Wno-typedef-redefinition
3 | OPT= -O2 -std=c99
4 |
5 | R_CFLAGS= $(STD) $(WARN) $(OPT) $(DEBUG) $(CFLAGS)
6 | R_LDFLAGS= $(LDFLAGS)
7 | DEBUG= -g
8 |
9 | R_CC=$(CC) $(R_CFLAGS)
10 | R_LD=$(CC) $(R_LDFLAGS)
11 |
12 | all: geom.o grisu3.o rtree.o geoutil.o \
13 | poly.o polyinside.o polyraycast.o polyintersects.o \
14 | hash.o bing.o json.o
15 | testapp: all
16 | -@$(R_CC) -o test test.c grisu3.o -I. \
17 | geom_test.c geom.o \
18 | rtree_test.c rtree.o \
19 | geoutil_test.c geoutil.o \
20 | json.o \
21 | polyinside_test.c polyintersects_test.c poly_test.c \
22 | poly.o polyinside.o polyraycast.o polyintersects.o \
23 | -lm
24 |
25 | test: testapp
26 | -@./test
27 |
28 | .PHONY: all
29 |
30 | geom.o: geom.h geom.c geom_levels.c geom_polymap.c geom_json.c
31 | grisu3.o: grisu3.h grisu3.c
32 | rtree.o: rtree.h rtree.c rtree_tmpl.c
33 | geoutil.o: geoutil.h geoutil.c
34 | poly.o: poly.h poly.c
35 | polyinside.o: poly.h polyinside.c
36 | polyraycast.o: poly.h polyraycast.c
37 | polyintersects.o: poly.h polyintersects.c
38 | hash.o: hash.h hash.c
39 | bing.o: bing.h bing.c
40 | json.o: json.h json.c
41 |
42 | .c.o:
43 | $(R_CC) -c $<
44 |
45 | clean:
46 | rm -f *.o
47 |
--------------------------------------------------------------------------------
/src/spatial/bing.c:
--------------------------------------------------------------------------------
1 | // https://msdn.microsoft.com/en-us/library/bb259689.aspx
2 |
3 | #include
4 | #include
5 | #include "bing.h"
6 |
7 | #ifndef PI
8 | #define PI 3.14159265358979323846
9 | #endif
10 |
11 |
12 | //static const double EarthRadius = 6378137.0;
13 | static const double MinLatitude = -85.05112878;
14 | static const double MaxLatitude = 85.05112878;
15 | static const double MinLongitude = -180.0;
16 | static const double MaxLongitude = 180.0;
17 | static const int TileSize = 256;
18 | //static const int MaxLevelOfDetail = 38;
19 |
20 |
21 | static double clip(double n, double minValue, double maxValue) {
22 | if (n < minValue) {
23 | return minValue;
24 | }
25 | if (n > maxValue) {
26 | return maxValue;
27 | }
28 | return n;
29 | }
30 |
31 | int bingMapSize(int levelOfDetail) {
32 | return TileSize << levelOfDetail;
33 | }
34 |
35 | void bingLatLongToPixelXY(double latitude, double longitude, int levelOfDetail, int *pixelX, int *pixelY) {
36 | latitude = clip(latitude, MinLatitude, MaxLatitude);
37 | longitude = clip(longitude, MinLongitude, MaxLongitude);
38 | double x = (longitude + 180) / 360;
39 | double sinLatitude = sin(latitude * PI / 180);
40 | double y = 0.5 - log((1+sinLatitude)/(1-sinLatitude))/(4*PI);
41 | double mapSize = bingMapSize(levelOfDetail);
42 | if (pixelX) *pixelX = clip(x*mapSize+0.5, 0, mapSize-1);
43 | if (pixelY) *pixelY = clip(y*mapSize+0.5, 0, mapSize-1);
44 | }
45 |
46 | void bingPixelXYToLatLong(int pixelX, int pixelY, int levelOfDetail, double *latitude, double *longitude) {
47 | double mapSize = bingMapSize(levelOfDetail);
48 | double x = (clip(pixelX, 0, mapSize-1) / mapSize) - 0.5;
49 | double y = 0.5 - (clip(pixelY, 0, mapSize-1) / mapSize);
50 | if (latitude) *latitude = 90 - 360*atan(exp(-y*2*PI))/PI;
51 | if (longitude) *longitude = 360 * x;
52 | }
53 |
54 | void bingPixelXYToTileXY(int pixelX, int pixelY, int *tileX, int *tileY) {
55 | if (tileX) *tileX = pixelX >> 8;
56 | if (tileY) *tileY = pixelY >> 8;
57 | }
58 |
59 | void bingTileXYToPixelXY(int tileX, int tileY, int *pixelX, int *pixelY) {
60 | if (pixelX) *pixelX = tileX << 8;
61 | if (pixelY) *pixelY = tileY << 8;
62 | }
63 |
64 | void bingTileXYToQuadKey(int tileX, int tileY, int levelOfDetail, char *quadKey) {
65 | for (int i=levelOfDetail,j=0;i>0;i--,j++) {
66 | int mask = 1 << (i - 1);
67 | if ((tileX & mask) != 0) {
68 | if ((tileY & mask) != 0) {
69 | quadKey[j] = '3';
70 | } else {
71 | quadKey[j] = '1';
72 | }
73 | } else if ((tileY & mask) != 0) {
74 | quadKey[j] = '2';
75 | } else {
76 | quadKey[j] = '0';
77 | }
78 | }
79 | }
80 |
81 | int bingQuadKeyToTileXY(char *quadKey, int *tileX, int *tileY, int *levelOfDetail) {
82 | int ltileX = 0;
83 | int ltileY = 0;
84 | int llevelOfDetail = strlen(quadKey);
85 | for (int i=llevelOfDetail;i>0;i--) {
86 | int mask = 1 << (i - 1);
87 | switch (quadKey[llevelOfDetail-i]) {
88 | case '0':
89 | break;
90 | case '1':
91 | ltileX |= mask;
92 | break;
93 | case '2':
94 | ltileY |= mask;
95 | break;
96 | case '3':
97 | ltileX |= mask;
98 | ltileY |= mask;
99 | break;
100 | default:
101 | return 0;
102 | }
103 | }
104 | if (tileX) *tileX = ltileX;
105 | if (tileY) *tileY = ltileY;
106 | if (levelOfDetail) *levelOfDetail = llevelOfDetail;
107 |
108 | return 1;
109 | }
110 |
111 | void bingTileXYToBounds(int tileX, int tileY, int levelOfDetail, double *minLat, double *minLon, double *maxLat, double *maxLon) {
112 | int size = 1 << levelOfDetail;
113 | int pixelX, pixelY;
114 | bingTileXYToPixelXY(tileX, tileY, &pixelX, &pixelY);
115 | bingPixelXYToLatLong(pixelX, pixelY, levelOfDetail, maxLat, minLon);
116 | bingTileXYToPixelXY(tileX+1, tileY+1, &pixelX, &pixelY);
117 | bingPixelXYToLatLong(pixelX, pixelY, levelOfDetail, minLat, maxLon);
118 | if (tileX%size == 0) {
119 | if (minLon) *minLon = MinLongitude;
120 | }
121 | if (tileX%size == size-1) {
122 | if (maxLon) *maxLon = MaxLongitude;
123 | }
124 | if (tileY <= 0) {
125 | if (maxLat) *maxLat = MaxLatitude;
126 | }
127 | if (tileY >= size-1) {
128 | if (minLat) *minLat = MinLatitude;
129 | }
130 | }
131 |
132 | int bingQuadKeyToBounds(char *quadkey, double *minLat, double *minLon, double *maxLat, double *maxLon){
133 | int tileX, tileY, levelOfDetail;
134 | if (!bingQuadKeyToTileXY(quadkey, &tileX, &tileY, &levelOfDetail)){
135 | return 0;
136 | }
137 | bingTileXYToBounds(tileX, tileY, levelOfDetail, minLat, minLon, maxLat, maxLon);
138 | return 1;
139 | }
140 |
141 | void bingLatLonToTileXY(double latitude, double longitude, int levelOfDetail, int *tileX, int *tileY){
142 | int pixelX, pixelY;
143 | bingLatLongToPixelXY(latitude, longitude, levelOfDetail, &pixelX, &pixelY);
144 | bingPixelXYToTileXY(pixelX, pixelY, tileX, tileY);
145 | }
146 |
147 | void bingLatLongToQuadKey(double latitude, double longitude, int levelOfDetail, char *quadkey){
148 | int tileX, tileY;
149 | bingLatLonToTileXY(latitude, longitude, levelOfDetail, &tileX, &tileY);
150 | bingTileXYToQuadKey(tileX, tileY, levelOfDetail, quadkey);
151 | }
152 |
153 |
154 |
155 |
156 |
157 |
158 |
--------------------------------------------------------------------------------
/src/spatial/bing.h:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2016, Josh Baker .
3 | * All rights reserved.
4 | *
5 | * Redistribution and use in source and binary forms, with or without
6 | * modification, are permitted provided that the following conditions are met:
7 | *
8 | * * Redistributions of source code must retain the above copyright notice,
9 | * this list of conditions and the following disclaimer.
10 | * * Redistributions in binary form must reproduce the above copyright
11 | * notice, this list of conditions and the following disclaimer in the
12 | * documentation and/or other materials provided with the distribution.
13 | * * Neither the name of Redis nor the names of its contributors may be used
14 | * to endorse or promote products derived from this software without
15 | * specific prior written permission.
16 | *
17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
21 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
27 | * THE POSSIBILITY OF SUCH DAMAGE.
28 | */
29 |
30 | #ifndef BING_H_
31 | #define BING_H_
32 |
33 | #if defined(__cplusplus)
34 | extern "C" {
35 | #endif
36 |
37 | int bingMapSize(int levelOfDetail);
38 | void bingLatLongToPixelXY(double latitude, double longitude, int levelOfDetail, int *pixelX, int *pixelY);
39 | void bingPixelXYToLatLong(int pixelX, int pixelY, int levelOfDetail, double *latitude, double *longitude);
40 | void bingPixelXYToTileXY(int pixelX, int pixelY, int *tileX, int *tileY);
41 | void bingTileXYToPixelXY(int tileX, int tileY, int *pixelX, int *pixelY);
42 | void bingTileXYToQuadKey(int tileX, int tileY, int levelOfDetail, char *quadKey);
43 | int bingQuadKeyToTileXY(char *quadKey, int *tileX, int *tileY, int *levelOfDetail);
44 | void bingTileXYToBounds(int tileX, int tileY, int levelOfDetail, double *minLat, double *minLon, double *maxLat, double *maxLon);
45 | int bingQuadKeyToBounds(char *quadkey, double *minLat, double *minLon, double *maxLat, double *maxLon);
46 | void bingLatLongToQuadKey(double latitude, double longitude, int levelOfDetail, char *quadkey);
47 | void bingLatLonToTileXY(double latitude, double longitude, int levelOfDetail, int *tileX, int *tileY);
48 |
49 |
50 | #if defined(__cplusplus)
51 | }
52 | #endif
53 | #endif /* BING_H_ */
54 |
--------------------------------------------------------------------------------
/src/spatial/geom.h:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2016, Josh Baker .
3 | * All rights reserved.
4 | *
5 | * Redistribution and use in source and binary forms, with or without
6 | * modification, are permitted provided that the following conditions are met:
7 | *
8 | * * Redistributions of source code must retain the above copyright notice,
9 | * this list of conditions and the following disclaimer.
10 | * * Redistributions in binary form must reproduce the above copyright
11 | * notice, this list of conditions and the following disclaimer in the
12 | * documentation and/or other materials provided with the distribution.
13 | * * Neither the name of Redis nor the names of its contributors may be used
14 | * to endorse or promote products derived from this software without
15 | * specific prior written permission.
16 | *
17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
21 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
27 | * THE POSSIBILITY OF SUCH DAMAGE.
28 | */
29 |
30 | #ifndef GEOM_H_
31 | #define GEOM_H_
32 |
33 | #if defined(__cplusplus)
34 | extern "C" {
35 | #endif
36 |
37 | #include
38 | #include
39 | #include "poly.h"
40 |
41 | typedef enum {
42 | GEOM_UNKNOWN = 0,
43 | GEOM_POINT = 1,
44 | GEOM_LINESTRING = 2,
45 | GEOM_POLYGON = 3,
46 | GEOM_MULTIPOINT = 4,
47 | GEOM_MULTILINESTRING = 5,
48 | GEOM_MULTIPOLYGON = 6,
49 | GEOM_GEOMETRYCOLLECTION = 7,
50 | } geomType;
51 |
52 | #define GEOM_VALID_TYPE(t) ((t)>=GEOM_POINT&&(t)<=GEOM_GEOMETRYCOLLECTION)
53 |
54 | typedef enum {
55 | GEOM_ERR_NONE,
56 | GEOM_ERR_UNSUPPORTED,
57 | GEOM_ERR_MEMORY,
58 | GEOM_ERR_INPUT,
59 | } geomErr;
60 |
61 | const char *geomErrText(geomErr err);
62 |
63 | typedef enum {
64 | GEOM_WKT_SHOW_ZM = 1<<0, // Show 'Z' and 'M' for 3D/4D points.
65 | GEOM_WKT_SHOW_EMPTY = 2<<0, // Use 'EMPTY' instead of '()'
66 | } geomWKTEncodeOpts;
67 |
68 | typedef enum {
69 | GEOM_WKT_LEAVE_OPEN = 1<<0, // Used when parsing collections.
70 | GEOM_WKT_REQUIRE_ZM = 1<<1, // 'Z' and 'M' are required for 3D/4D points.
71 | } geomWKTDecodeOpts;
72 |
73 | typedef char *geom;
74 |
75 | typedef struct geomCoord {
76 | double x, y, z, m;
77 | } geomCoord;
78 |
79 | int geomCoordString(geomCoord c, int withZ, int withM, char *str);
80 |
81 | typedef struct geomRect {
82 | geomCoord max, min;
83 | } geomRect;
84 |
85 | geomCoord geomRectCenter(geomRect r);
86 | geomRect geomRectExpand(geomRect r, geomCoord c);
87 | geomRect geomRectUnion(geomRect r1, geomRect r2);
88 | int geomRectString(geomRect r, int withZ, int withM, char *str);
89 | geomErr geomDecodeWKT(const char *input, geomWKTDecodeOpts opts, geom *g, int *size);
90 | geomErr geomDecodeWKB(const void *input, size_t length, geom *g, int *size);
91 | geomErr geomDecode(const void *input, size_t length, geomWKTDecodeOpts opts, geom *g, int *size);
92 | void geomFree(geom g);
93 |
94 | char *geomEncodeWKT(geom g, geomWKTEncodeOpts opts);
95 | void geomFreeWKT(char *wkt);
96 | char *geomEncodeJSON(geom g);
97 | void geomFreeJSON(char *json);
98 |
99 | geomType geomGetType(geom g);
100 | geomCoord geomCenter(geom g);
101 | geomRect geomBounds(geom g);
102 |
103 | typedef struct geomIterator {
104 | int count;
105 | uint8_t *ptr;
106 | geom g;
107 | int sz;
108 | } geomIterator;
109 |
110 | geomErr geomGeometryCollectionIterator(geom g, geomIterator *itr);
111 | int geomIteratorNext(geomIterator *itr);
112 | int geomIteratorValues(geomIterator *itr, geom *g, int *sz);
113 |
114 | geom *geomGeometryCollectionFlattenedArray(geom g, int *count);
115 | void geomFreeFlattenedArray(geom *garr);
116 | geom geomNewCirclePolygon(geomCoord center, double meters, int steps, int *size);
117 | geom geomNewRectPolygon(geomRect rect, int *size);
118 | int geomIsSimplePoint(geom g);
119 | int geomCoordWithinRadius(geomCoord c, geomCoord center, double meters);
120 |
121 |
122 |
123 | /* geomPolyMap is flattened representation of a geometry.
124 | * Each geometry is reduced down to a POINT, LINESTRING, or POLYGON.
125 | * The resulting polygons are stored in the 'polygons' array.
126 | * The number of polygons in the array is specified in 'polygonCount'.
127 | * The 'holes' and 'types' arrays will have 'polygonCount' number of elements.
128 | *
129 | * For example, a simple GEOM_POINT converted to a PolyMap will have
130 | * one element in the 'polygons' array that contains the point data, one
131 | * element in the 'holes' array that is empty, and one element in the
132 | * types array that is GEOM_POINT.
133 | */
134 | typedef struct geomPolyMap{
135 | geom g; // first geometry.
136 | geomType type; // type of the first geometry.
137 | int z,m; // indicates that z or m are provided.
138 | int dims; // number of dimensions per point.
139 | int collection; // the geometries are on the heap.
140 | int multipoly; // the polygons and holes are on the heap.
141 | int shared; // this structure does not need to be freed.
142 |
143 | // geoms
144 | int geomCount; // the number of geometries.
145 | geom *geoms; // one or more geometries.
146 |
147 | // polys
148 | polyPoint center; // center point of the first geometry.
149 | polyRect bounds; // minimum bounding rect of the first geometry.
150 | int polygonCount; // number of polygons and holes.
151 | polyPolygon *polygons; // all of the polygons belonging to the geometry.
152 | polyMultiPolygon *holes; // all of the holes belonging to the geometry.
153 | geomType *types; // the geometry type for each polygon/holes.
154 |
155 | // some private vars
156 | polyPolygon ppoly;
157 | polyMultiPolygon pholes;
158 | } geomPolyMap;
159 |
160 | void geomFreePolyMap(geomPolyMap *m);
161 | geomPolyMap *geomNewPolyMap(geom g);
162 |
163 | int geomPolyMapIntersects(geomPolyMap *m1, geomPolyMap *m2);
164 | int geomPolyMapWithin(geomPolyMap *m1, geomPolyMap *m2);
165 |
166 | int geomPolyMapContains(geomPolyMap *m1, geomPolyMap *m2);
167 | int geomPolyMapExIntersects(geomPolyMap *m1, geomPolyMap *m2);
168 |
169 | #if defined(__cplusplus)
170 | }
171 | #endif
172 | #endif /* GEOM_H_ */
173 |
--------------------------------------------------------------------------------
/src/spatial/geom_json.c:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2016, Josh Baker .
3 | * All rights reserved.
4 | *
5 | * Redistribution and use in source and binary forms, with or without
6 | * modification, are permitted provided that the following conditions are met:
7 | *
8 | * * Redistributions of source code must retain the above copyright notice,
9 | * this list of conditions and the following disclaimer.
10 | * * Redistributions in binary form must reproduce the above copyright
11 | * notice, this list of conditions and the following disclaimer in the
12 | * documentation and/or other materials provided with the distribution.
13 | * * Neither the name of Redis nor the names of its contributors may be used
14 | * to endorse or promote products derived from this software without
15 | * specific prior written permission.
16 | *
17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
21 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
27 | * THE POSSIBILITY OF SUCH DAMAGE.
28 | */
29 |
30 | #ifndef FROM_GEOM_C
31 | #error This is not a standalone source file.
32 | #endif
33 |
34 | static int jsonDecodeAny(json_value *value, geom *g, int *size);
35 |
36 | static uint8_t *makeBOID(int cap, int id, int dims){
37 | if (cap < 5){
38 | return NULL;
39 | }
40 | uint8_t *b = zmalloc(cap);
41 | if (!b){
42 | return NULL;
43 | }
44 | if (LITTLE_ENDIAN){
45 | b[0] = 0x01;
46 | } else{
47 | b[0] = 0x00;
48 | }
49 | switch (dims){
50 | case 2:
51 | *((uint32_t*)(b+1)) = id;
52 | break;
53 | case 3:
54 | *((uint32_t*)(b+1)) = 1000+id;
55 | break;
56 | case 4:
57 | *((uint32_t*)(b+1)) = 3000+id;
58 | break;
59 | }
60 | return b;
61 | }
62 |
63 | static int jsonGetPosition(json_value *value, int *dims, geomCoord *position){
64 | if (!value||
65 | value->type!=json_array||
66 | value->u.array.length<2||
67 | value->u.array.length>4
68 | ){
69 | return 0;
70 | }
71 | geomCoord coord = {0,0,0,0};
72 | for (int i=0;iu.array.length;i++){
73 | double n = 0;
74 | json_value *nvalue = value->u.array.values[i];
75 | if (nvalue->type==json_double){
76 | n = nvalue->u.dbl;
77 | } else if (nvalue->type==json_integer){
78 | n = nvalue->u.integer;
79 | } else {
80 | return 0;
81 | }
82 | switch (i){
83 | case 0:
84 | coord.x = n;
85 | break;
86 | case 1:
87 | coord.y = n;
88 | break;
89 | case 2:
90 | coord.z = n;
91 | break;
92 | case 4:
93 | coord.m = n;
94 | break;
95 | }
96 | }
97 | if (dims) *dims = value->u.array.length;
98 | if (position) *position = coord;
99 | return 1;
100 | }
101 |
102 | static int jsonDecodePoint(json_value *coords, geom *g, int *size){
103 | if (!coords||coords->type!=json_array){
104 | return 0;
105 | }
106 | int dims;
107 | geomCoord position;
108 | if (!jsonGetPosition(coords, &dims, &position)){
109 | return 0;
110 | }
111 | uint8_t *b = makeBOID(5+dims*8, 1, dims);
112 | if (!b){
113 | return 0;
114 | }
115 | ((double*)(b+5))[0] = position.x;
116 | ((double*)(b+5))[1] = position.y;
117 | if (dims>2){
118 | ((double*)(b+5))[2] = position.z;
119 | if (dims>3){
120 | ((double*)(b+5))[3] = position.m;
121 | }
122 | }
123 | if (g) *g = (geom)b;
124 | if (size) *size = 5+dims*8;
125 | return 1;
126 | }
127 |
128 | #define JD_BEGIN(type)\
129 | int rdims = 0;\
130 | int cap = 5;\
131 | uint8_t *b = zmalloc(cap);\
132 | if (!b){\
133 | goto err;\
134 | }\
135 | if (LITTLE_ENDIAN){\
136 | b[0] = 0x01;\
137 | } else{\
138 | b[0] = 0x00;\
139 | }\
140 | *((uint32_t*)(b+1)) = (type);\
141 | uint8_t *ptr = b+5;\
142 | rdims = rdims;
143 |
144 | #define JD_EXPAND_BUFFER(n){\
145 | int ncap = cap+(n);\
146 | uint8_t *nb = zrealloc(b, ncap);\
147 | if (!nb){\
148 | goto err;\
149 | }\
150 | ptr = nb+(ptr-b);\
151 | cap = ncap;\
152 | b = nb;\
153 | }
154 |
155 | #define JD_WRITE_INT(n)\
156 | JD_EXPAND_BUFFER(4);\
157 | *((uint32_t*)ptr) = (n);\
158 | ptr+=4;
159 |
160 | #define JD_WRITE_DBL(d)\
161 | JD_EXPAND_BUFFER(8);\
162 | *((double*)ptr) = (d);\
163 | ptr+=8;
164 |
165 | #define JD_END()\
166 | if (g) *g = (geom)b;\
167 | if (size) *size = ptr-b;\
168 | return 1;\
169 | err:\
170 | if (b){\
171 | zfree(b);\
172 | }\
173 | return 0;
174 |
175 |
176 | #define JD_WRITE_COORD(value){\
177 | int dims;\
178 | geomCoord position;\
179 | if (!jsonGetPosition((value), &dims, &position)){\
180 | goto err;\
181 | }\
182 | if (rdims==0){\
183 | rdims=dims;\
184 | if (rdims!=2){\
185 | if (rdims==3){\
186 | *((uint32_t*)(b+1)) += 1000;\
187 | } else if (rdims==4){\
188 | *((uint32_t*)(b+1)) += 3000;\
189 | }\
190 | }\
191 | } else if (dims!=rdims){\
192 | goto err;\
193 | }\
194 | JD_WRITE_DBL(position.x);\
195 | JD_WRITE_DBL(position.y);\
196 | if (dims>2){\
197 | JD_WRITE_DBL(position.z);\
198 | if (dims>3){\
199 | JD_WRITE_DBL(position.m);\
200 | }\
201 | }\
202 | }
203 |
204 | #define JD_WRITE_MULTI1_COORDS(coords){\
205 | if (!(coords)||(coords)->type!=json_array){\
206 | goto err;\
207 | }\
208 | JD_WRITE_INT((coords)->u.array.length);\
209 | for (int i=0;i<(coords)->u.array.length;i++){\
210 | json_value *nvalue1 = (coords)->u.array.values[i];\
211 | JD_WRITE_COORD(nvalue1);\
212 | }\
213 | }
214 |
215 | #define JD_WRITE_MULTI2_COORDS(coords){\
216 | if (!(coords)||(coords)->type!=json_array){\
217 | goto err;\
218 | }\
219 | JD_WRITE_INT((coords)->u.array.length);\
220 | for (int i=0;i<(coords)->u.array.length;i++){\
221 | json_value *nvalue2 = (coords)->u.array.values[i];\
222 | JD_WRITE_MULTI1_COORDS(nvalue2);\
223 | }\
224 | }
225 |
226 | #define JD_WRITE_MULTI3_COORDS(coords){\
227 | if (!(coords)||(coords)->type!=json_array){\
228 | goto err;\
229 | }\
230 | JD_WRITE_INT((coords)->u.array.length);\
231 | for (int i=0;i<(coords)->u.array.length;i++){\
232 | json_value *nvalue3 = (coords)->u.array.values[i];\
233 | JD_WRITE_MULTI2_COORDS(nvalue3);\
234 | }\
235 | }
236 |
237 |
238 | static int jsonDecodeLineString(json_value *coords, geom *g, int *size){
239 | JD_BEGIN(2);
240 | JD_WRITE_MULTI1_COORDS(coords);
241 | JD_END();
242 | }
243 | static int jsonDecodePolygon(json_value *coords, geom *g, int *size){
244 | JD_BEGIN(3);
245 | JD_WRITE_MULTI2_COORDS(coords);
246 | JD_END();
247 | }
248 | static int jsonDecodeMultiPoint(json_value *coords, geom *g, int *size){
249 | JD_BEGIN(4);
250 | JD_WRITE_MULTI1_COORDS(coords);
251 | JD_END();
252 | }
253 | static int jsonDecodeMultiLineString(json_value *coords, geom *g, int *size){
254 | JD_BEGIN(5);
255 | JD_WRITE_MULTI2_COORDS(coords);
256 | JD_END();
257 | }
258 | static int jsonDecodeMultiPolygon(json_value *coords, geom *g, int *size){
259 | JD_BEGIN(6);
260 | JD_WRITE_MULTI3_COORDS(coords);
261 | JD_END();
262 | }
263 |
264 | static int jsonDecodeGeometryCollection(json_value *geometries, geom *g, int *size){
265 | JD_BEGIN(7);
266 |
267 | if (!(geometries)||(geometries)->type!=json_array){
268 | goto err;
269 | }
270 | JD_WRITE_INT((geometries)->u.array.length);
271 | for (int i=0;i<(geometries)->u.array.length;i++){
272 | geom ng;
273 | int nsize;
274 | if (!jsonDecodeAny((geometries)->u.array.values[i], &ng, &nsize)){
275 | goto err;
276 | }
277 | JD_EXPAND_BUFFER(nsize);
278 | memcpy(ptr, (uint8_t*)ng, nsize);
279 | ptr+=nsize;
280 | geomFree(ng);
281 | }
282 |
283 | JD_END();
284 | }
285 |
286 | static int jsonDecodeAny(json_value *value, geom *g, int *size){
287 | if (!value||
288 | value->type != json_object){
289 | return 0;
290 | }
291 | char *type = "";
292 | json_value *coordinates = NULL;
293 | json_value *geometries = NULL;
294 | for (int i=0;iu.object.length;i++){
295 | if (!strcmp(value->u.object.values[i].name, "type")){
296 | if (value->u.object.values[i].value->type == json_string){
297 | type = value->u.object.values[i].value->u.string.ptr;
298 | }
299 | }
300 | if (!strcmp(value->u.object.values[i].name, "coordinates")){
301 | if (value->u.object.values[i].value->type == json_array){
302 | coordinates = value->u.object.values[i].value;
303 | }
304 | }
305 | if (!strcmp(value->u.object.values[i].name, "geometries")){
306 | if (value->u.object.values[i].value->type == json_array){
307 | geometries = value->u.object.values[i].value;
308 | }
309 | }
310 | }
311 | if (!strcmp(type, "Point")){
312 | return jsonDecodePoint(coordinates, g, size);
313 | } else if (!strcmp(type, "MultiPoint")){
314 | return jsonDecodeMultiPoint(coordinates, g, size);
315 | } else if (!strcmp(type, "LineString")){
316 | return jsonDecodeLineString(coordinates, g, size);
317 | } else if (!strcmp(type, "MultiLineString")){
318 | return jsonDecodeMultiLineString(coordinates, g, size);
319 | } else if (!strcmp(type, "Polygon")){
320 | return jsonDecodePolygon(coordinates, g, size);
321 | } else if (!strcmp(type, "MultiPolygon")){
322 | return jsonDecodeMultiPolygon(coordinates, g, size);
323 | } else if (!strcmp(type, "GeometryCollection")){
324 | return jsonDecodeGeometryCollection(geometries, g, size);
325 | }
326 | return 0;
327 | }
328 |
329 |
330 |
331 |
--------------------------------------------------------------------------------
/src/spatial/geom_levels.c:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2016, Josh Baker .
3 | * All rights reserved.
4 | *
5 | * Redistribution and use in source and binary forms, with or without
6 | * modification, are permitted provided that the following conditions are met:
7 | *
8 | * * Redistributions of source code must retain the above copyright notice,
9 | * this list of conditions and the following disclaimer.
10 | * * Redistributions in binary form must reproduce the above copyright
11 | * notice, this list of conditions and the following disclaimer in the
12 | * documentation and/or other materials provided with the distribution.
13 | * * Neither the name of Redis nor the names of its contributors may be used
14 | * to endorse or promote products derived from this software without
15 | * specific prior written permission.
16 | *
17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
21 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
27 | * THE POSSIBILITY OF SUCH DAMAGE.
28 | */
29 |
30 | #ifndef FROM_GEOM_C
31 | #error This is not a standalone source file.
32 | #endif
33 |
34 | static geomRect levelAny_geomBounds(uint8_t *g, int *read);
35 | static geomCoord levelAny_geomCenter(uint8_t *g, int *read);
36 |
37 |
38 | static geomRect level1_getBounds(ghdr *h, uint8_t *g, int *read){
39 | geomRect r;
40 | r.min = readcoord(h, g, read);
41 | r.max = r.min;
42 | return r;
43 | }
44 |
45 | static geomCoord level1_getCenter(ghdr *h, uint8_t *g, int *read){
46 | return readcoord(h, g, read);
47 | }
48 |
49 | static geomRect level2_getBounds(ghdr *h, uint8_t *g, int *read){
50 | geomRect r;
51 | memset(&r, 0, sizeof(geomRect));
52 | int got = 0;
53 | uint8_t *p = g;
54 | int len = *((uint32_t*)p);
55 | int nread = 0;
56 | p += 4;
57 | for (int i=0;i.
3 | * All rights reserved.
4 | *
5 | * Redistribution and use in source and binary forms, with or without
6 | * modification, are permitted provided that the following conditions are met:
7 | *
8 | * * Redistributions of source code must retain the above copyright notice,
9 | * this list of conditions and the following disclaimer.
10 | * * Redistributions in binary form must reproduce the above copyright
11 | * notice, this list of conditions and the following disclaimer in the
12 | * documentation and/or other materials provided with the distribution.
13 | * * Neither the name of Redis nor the names of its contributors may be used
14 | * to endorse or promote products derived from this software without
15 | * specific prior written permission.
16 | *
17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
21 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
27 | * THE POSSIBILITY OF SUCH DAMAGE.
28 | */
29 |
30 | #ifndef FROM_GEOM_C
31 | #error This is not a standalone source file.
32 | #endif
33 |
34 |
35 | void geomFreePolyMap(geomPolyMap *m){
36 | if (m) {
37 | if (m->geoms && m->collection){
38 | geomFreeFlattenedArray(m->geoms);
39 | }
40 | if (m->polygons && m->multipoly){
41 | zfree(m->polygons);
42 | }
43 | if (m->holes && m->multipoly){
44 | zfree(m->holes);
45 | }
46 | if (m->types) {
47 | zfree(m->types);
48 | }
49 | zfree(m);
50 | }
51 | }
52 |
53 | static geomPolyMap *geomNewPolyMapBase(geom g){
54 | if (!g){
55 | return NULL;
56 | }
57 | uint8_t *ptr = (uint8_t*)g;
58 | if (*ptr!=0&&*ptr!=1){
59 | return NULL;
60 | }
61 | ptr++;
62 | ghdr h = readhdr(ptr);
63 | ptr+=4;
64 | if (h.type == GEOM_UNKNOWN){
65 | return NULL;
66 | }
67 | int dims = 2;
68 | if (h.z){
69 | dims++;
70 | }
71 | if (h.m){
72 | dims++;
73 | }
74 | geomPolyMap *m = zmalloc(sizeof(geomPolyMap));
75 | if (!m) {
76 | return NULL;
77 | }
78 | memset(m, 0, sizeof(geomPolyMap));
79 |
80 | m->g = g;
81 | geomCoord center = geomCenter(g);
82 | m->center.x = center.x;
83 | m->center.y = center.y;
84 | geomRect bounds = geomBounds(g);
85 | m->bounds.min.x = bounds.min.x;
86 | m->bounds.min.y = bounds.min.y;
87 | m->bounds.max.x = bounds.max.x;
88 | m->bounds.max.y = bounds.max.y;
89 | if (h.type==GEOM_GEOMETRYCOLLECTION){
90 | m->collection = 1;
91 | m->geoms = geomGeometryCollectionFlattenedArray(g, &m->geomCount);
92 | if (!m->geoms){
93 | goto err;
94 | }
95 | } else {
96 | m->geoms = &g;
97 | m->geomCount = 1;
98 | }
99 | m->type = h.type;
100 | m->z = h.z;
101 | m->m = h.m;
102 | m->dims = dims;
103 |
104 | switch (h.type){
105 | case GEOM_POINT:
106 | case GEOM_LINESTRING:
107 | switch (h.type){
108 | case GEOM_POINT:
109 | m->ppoly.len = 1;
110 | break;
111 | case GEOM_LINESTRING:
112 | m->ppoly.len = *((uint32_t*)ptr);
113 | ptr+=4;
114 | break;
115 | }
116 | m->ppoly.dims = dims;
117 | m->ppoly.values = (double*)ptr;
118 | m->polygonCount = 1;
119 | m->polygons = &m->ppoly;
120 | m->holes = &m->pholes;
121 | m->types = zmalloc(sizeof(geomType));
122 | if (!m->types){
123 | goto err;
124 | }
125 | *(m->types) = m->type;
126 | break;
127 | case GEOM_POLYGON:
128 | single_polygon:{
129 | int count = *((uint32_t*)ptr);
130 | ptr+=4;
131 | if (count>0){
132 | m->ppoly.len = *((uint32_t*)ptr);
133 | ptr+=4;
134 | m->ppoly.dims = dims;
135 | m->ppoly.values = (double*)ptr;
136 | ptr+=dims*8*m->ppoly.len;
137 | if (count>1){
138 | m->pholes.len = count-1;
139 | m->pholes.dims = dims;
140 | m->pholes.values = (double*)ptr;
141 | }
142 | }
143 | m->polygonCount = 1;
144 | m->polygons = &m->ppoly;
145 | m->holes = &m->pholes;
146 | m->types = zmalloc(sizeof(geomType));
147 | if (!m->types){
148 | goto err;
149 | }
150 | *(m->types) = m->type;
151 | break;
152 | }
153 | case GEOM_MULTIPOINT:{
154 | int count = *((uint32_t*)ptr);
155 | ptr+=4;
156 | m->multipoly = 1;
157 | m->polygonCount = count;
158 | m->polygons = zmalloc(count*sizeof(polyPolygon));
159 | if (!m->polygons){
160 | goto err;
161 | }
162 | memset(m->polygons, 0, count*sizeof(polyPolygon));
163 | m->holes = zmalloc(count*sizeof(polyMultiPolygon));
164 | if (!m->holes){
165 | goto err;
166 | }
167 | memset(m->holes, 0, count*sizeof(polyMultiPolygon));
168 | m->types = zmalloc(count*sizeof(geomType));
169 | if (!m->types){
170 | goto err;
171 | }
172 | memset(m->types, 0, count*sizeof(geomType));
173 | for (int i=0;ipolygons[i].len = 1;
175 | m->polygons[i].dims = dims;
176 | m->polygons[i].values = (double*)ptr;
177 | ptr+=dims*8;
178 | m->types[i] = GEOM_POINT; // reduce to a point
179 | }
180 | break;
181 | }
182 | case GEOM_MULTILINESTRING:{
183 | int count = *((uint32_t*)ptr);
184 | ptr+=4;
185 | m->multipoly = 1;
186 | m->polygonCount = count;
187 | m->polygons = zmalloc(count*sizeof(polyPolygon));
188 | if (!m->polygons){
189 | goto err;
190 | }
191 | memset(m->polygons, 0, count*sizeof(polyPolygon));
192 | m->holes = zmalloc(count*sizeof(polyMultiPolygon));
193 | if (!m->holes){
194 | goto err;
195 | }
196 | memset(m->holes, 0, count*sizeof(polyMultiPolygon));
197 | m->types = zmalloc(count*sizeof(geomType));
198 | if (!m->types){
199 | goto err;
200 | }
201 | memset(m->types, 0, count*sizeof(geomType));
202 | for (int i=0;ipolygons[i].len = *((uint32_t*)ptr);
204 | ptr+=4;
205 | m->polygons[i].dims = dims;
206 | m->polygons[i].values = (double*)ptr;
207 | ptr+=dims*8*m->polygons[i].len;
208 | m->types[i] = GEOM_LINESTRING; // reduce to a linestring
209 | }
210 | break;
211 | }
212 | case GEOM_MULTIPOLYGON:{
213 | int count = *((uint32_t*)ptr);
214 | if (count<=1){
215 | goto single_polygon;
216 | }
217 | ptr+=4;
218 | m->multipoly = 1;
219 | m->polygonCount = count;
220 | m->polygons = zmalloc(count*sizeof(polyPolygon));
221 | if (!m->polygons){
222 | goto err;
223 | }
224 | memset(m->polygons, 0, count*sizeof(polyPolygon));
225 | m->holes = zmalloc(count*sizeof(polyMultiPolygon));
226 | if (!m->holes){
227 | goto err;
228 | }
229 | memset(m->holes, 0, count*sizeof(polyMultiPolygon));
230 | m->types = zmalloc(count*sizeof(geomType));
231 | if (!m->types){
232 | goto err;
233 | }
234 | memset(m->types, 0, count*sizeof(geomType));
235 | for (int i=0;ipolygons[i].len = *((uint32_t*)ptr);
242 | ptr+=4;
243 | m->polygons[i].dims = dims;
244 | m->polygons[i].values = (double*)ptr;
245 | ptr+=dims*8*m->polygons[i].len;
246 | if (count2>1){
247 | m->holes[i].len = count2-1;
248 | m->holes[i].dims = dims;
249 | m->holes[i].values = (double*)ptr;
250 | for (int j=1;jtypes[i] = GEOM_POLYGON; // reduce to a polygon
257 | }
258 | break;
259 | }
260 | case GEOM_GEOMETRYCOLLECTION:{
261 | int cap = 1;
262 | int len = 0;
263 | polyPolygon *polygons = NULL;
264 | polyMultiPolygon *holes = NULL;
265 | geomType *types = NULL;
266 | geomPolyMap *m2 = NULL;
267 | polygons = zmalloc(cap*sizeof(polyPolygon));
268 | if (!polygons){
269 | goto err2;
270 | }
271 | holes = zmalloc(cap*sizeof(polyMultiPolygon));
272 | if (!holes){
273 | goto err2;
274 | }
275 | types = zmalloc(cap*sizeof(geomType));
276 | if (!types){
277 | goto err2;
278 | }
279 | for (int i=0;igeomCount;i++){
280 | geom g2 = m->geoms[i];
281 | m2 = geomNewPolyMap(g2);
282 | if (!m2){
283 | goto err2;
284 | }
285 | for (int j=0;jpolygonCount;j++){
286 | if (len==cap){
287 | int ncap = cap==0?1:cap*2;
288 | polyPolygon *npolygons = zrealloc(polygons, ncap*sizeof(polyPolygon));
289 | if (!npolygons){
290 | goto err2;
291 | }
292 | polyMultiPolygon *nholes = zrealloc(holes, ncap*sizeof(polyMultiPolygon));
293 | if (!nholes){
294 | goto err2;
295 | }
296 | geomType *ntypes = zrealloc(types, ncap*sizeof(geomType));
297 | if (!ntypes){
298 | goto err2;
299 | }
300 | polygons = npolygons;
301 | holes = nholes;
302 | types = ntypes;
303 | cap = ncap;
304 | }
305 | polygons[len] = m2->polygons[j];
306 | holes[len] = m2->holes[j];
307 | types[len] = m2->types[j];
308 | len++;
309 | }
310 | geomFreePolyMap(m2);
311 | m2 = NULL;
312 | }
313 | m->multipoly = 1;
314 | m->polygonCount = len;
315 |
316 | m->polygons = polygons;
317 | m->holes = holes;
318 | m->types = types;
319 | break;
320 | err2:
321 | if (polygons){
322 | zfree(polygons);
323 | }
324 | if (holes){
325 | zfree(holes);
326 | }
327 | if (types){
328 | zfree(types);
329 | }
330 | if (m2){
331 | geomFreePolyMap(m2);
332 | }
333 | goto err;
334 | }}
335 | return m;
336 | err:
337 | if (m){
338 | geomFreePolyMap(m);
339 | }
340 | return NULL;
341 | }
342 |
343 | geomPolyMap *geomNewPolyMap(geom g){
344 | return geomNewPolyMapBase(g);
345 | }
346 |
--------------------------------------------------------------------------------
/src/spatial/geoutil.c:
--------------------------------------------------------------------------------
1 | /* Origin */
2 | /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
3 | /* Latitude/longitude spherical geodesy tools (c) Chris Veness 2002-2016 */
4 | /* MIT Licence */
5 | /* www.movable-type.co.uk/scripts/latlong.html */
6 | /* www.movable-type.co.uk/scripts/geodesy/docs/module-latlon-spherical.html */
7 | /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
8 |
9 | /* Modified and new stuff */
10 | /*
11 | * Copyright (c) 2016, Josh Baker .
12 | * All rights reserved.
13 | *
14 | * Redistribution and use in source and binary forms, with or without
15 | * modification, are permitted provided that the following conditions are met:
16 | *
17 | * * Redistributions of source code must retain the above copyright notice,
18 | * this list of conditions and the following disclaimer.
19 | * * Redistributions in binary form must reproduce the above copyright
20 | * notice, this list of conditions and the following disclaimer in the
21 | * documentation and/or other materials provided with the distribution.
22 | * * Neither the name of Redis nor the names of its contributors may be used
23 | * to endorse or promote products derived from this software without
24 | * specific prior written permission.
25 | *
26 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
27 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
28 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
29 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
30 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
31 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
32 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
33 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
34 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
35 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
36 | * THE POSSIBILITY OF SUCH DAMAGE.
37 | */
38 |
39 | #include
40 | #include
41 | #include "geoutil.h"
42 |
43 | #ifndef PI
44 | #define PI 3.14159265358979323846
45 | #endif
46 |
47 | #define EARTH_RADIUS 6372797.560856
48 | #define RAD(deg) ((deg)*PI/180.0)
49 | #define DEG(rad) ((rad)*180.0/PI)
50 |
51 | double geoutilDistance(double latA, double lonA, double latB, double lonB){
52 | double q1 = RAD(latA);
53 | double a1 = RAD(lonA);
54 | double q2 = RAD(latB);
55 | double a2 = RAD(lonB);
56 | double aq = q2 - q1;
57 | double av = a2 - a1;
58 | double a = sin(aq/2)*sin(aq/2) + cos(q1)*cos(q2)*sin(av/2)*sin(av/2);
59 | double c = 2 * atan2(sqrt(a), sqrt(1-a));
60 | return EARTH_RADIUS * c;
61 | }
62 |
63 | void geoutilDestinationLatLon(double lat, double lon, double distanceMeters, double bearingDegrees, double *destLat, double *destLon){
64 | if (!destLat || !destLon){
65 | return;
66 | }
67 | double tq = distanceMeters / EARTH_RADIUS; // angular distance in radians
68 | double Z = RAD(bearingDegrees);
69 | double q1 = RAD(lat);
70 | double a1 = RAD(lon);
71 | double q2 = asin(sin(q1)*cos(tq) + cos(q1)*sin(tq)*cos(Z));
72 | double a2 = a1 + atan2(sin(Z)*sin(tq)*cos(q1), cos(tq)-sin(q1)*sin(q2));
73 | a2 = fmod(a2+3*PI, 2*PI) - PI; // normalise to -180..+180°
74 | *destLat = DEG(q2);
75 | *destLon = DEG(a2);
76 | }
77 |
78 | geomRect geoutilBoundsFromLatLon(double centerLat, double centerLon, double distanceMeters){
79 | geomRect r;
80 | memset(&r, 0, sizeof(geomRect));
81 | double nLat, nLon, eLat, eLon, sLat, sLon, wLat, wLon;
82 | geoutilDestinationLatLon(centerLat, centerLon, distanceMeters, 0, &nLat, &nLon);
83 | geoutilDestinationLatLon(centerLat, centerLon, distanceMeters, 90, &eLat, &eLon);
84 | geoutilDestinationLatLon(centerLat, centerLon, distanceMeters, 180, &sLat, &sLon);
85 | geoutilDestinationLatLon(centerLat, centerLon, distanceMeters, 270, &wLat, &wLon);
86 | r.min.x = wLon;
87 | r.min.y = sLat;
88 | r.max.x = eLon;
89 | r.max.y = nLat;
90 | return r;
91 | }
92 |
93 |
94 |
--------------------------------------------------------------------------------
/src/spatial/geoutil.h:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2016, Josh Baker .
3 | * All rights reserved.
4 | *
5 | * Redistribution and use in source and binary forms, with or without
6 | * modification, are permitted provided that the following conditions are met:
7 | *
8 | * * Redistributions of source code must retain the above copyright notice,
9 | * this list of conditions and the following disclaimer.
10 | * * Redistributions in binary form must reproduce the above copyright
11 | * notice, this list of conditions and the following disclaimer in the
12 | * documentation and/or other materials provided with the distribution.
13 | * * Neither the name of Redis nor the names of its contributors may be used
14 | * to endorse or promote products derived from this software without
15 | * specific prior written permission.
16 | *
17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
21 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
27 | * THE POSSIBILITY OF SUCH DAMAGE.
28 | */
29 |
30 | #ifndef GEOUTIL_H_
31 | #define GEOUTIL_H_
32 |
33 | #if defined(__cplusplus)
34 | extern "C" {
35 | #endif
36 |
37 | #include "geom.h"
38 |
39 | double geoutilDistance(double latA, double lonA, double latB, double lonB);
40 | void geoutilDestinationLatLon(double lat, double lon, double distanceMeters, double bearingDegrees, double *destLat, double *destLon);
41 | geomRect geoutilBoundsFromLatLon(double centerLat, double centerLon, double distanceMeters);
42 |
43 | #if defined(__cplusplus)
44 | }
45 | #endif
46 |
47 | #endif /* GEOUTIL_H_ */
48 |
--------------------------------------------------------------------------------
/src/spatial/geoutil_test.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include "test.h"
4 | #include "geoutil.h"
5 |
6 | static double tdist(double latA, double latB, double lonA, double lonB){
7 | return floor(geoutilDistance(latA, latB, lonA, lonB)*100)/100;
8 | }
9 |
10 | int test_GeoUtilDistance(){
11 | assert(tdist(33, -115, 33.5, -115.5)==72476.64);
12 | return 1;
13 | }
14 |
15 | int test_GeoUtilDestination(){
16 | double lat, lon;
17 | geoutilDestinationLatLon(33, -115, 100000, 90, &lat, &lon);
18 | assert(fabs(lat - 32.995417)<0.00001 && fabs(lon - -113.927719)<0.00001);
19 | return 1;
20 | }
21 |
--------------------------------------------------------------------------------
/src/spatial/grisu3.c:
--------------------------------------------------------------------------------
1 | /* This file originated from https://github.com/juj/MathGeoLib
2 | * License: http://www.apache.org/licenses/LICENSE-2.0.html */
3 | /* This file is part of an implementation of the "grisu3" double to string
4 | conversion algorithm described in the research paper
5 |
6 | "Printing Floating-Point Numbers Quickly And Accurately with Integers"
7 | by Florian Loitsch, available at
8 | http://www.cs.tufts.edu/~nr/cs257/archive/florian-loitsch/printf.pdf */
9 |
10 | #include // uint64_t etc.
11 | #include // assert
12 | #include // ceil
13 | #include // sprintf
14 |
15 | #ifdef _MSC_VER
16 | #pragma warning(disable : 4204) // nonstandard extension used : non-constant aggregate initializer
17 | #endif
18 |
19 | #define D64_SIGN 0x8000000000000000ULL
20 | #define D64_EXP_MASK 0x7FF0000000000000ULL
21 | #define D64_FRACT_MASK 0x000FFFFFFFFFFFFFULL
22 | #define D64_IMPLICIT_ONE 0x0010000000000000ULL
23 | #define D64_EXP_POS 52
24 | #define D64_EXP_BIAS 1075
25 | #define DIYFP_FRACT_SIZE 64
26 | #define D_1_LOG2_10 0.30102999566398114 // 1 / lg(10)
27 | #define MIN_TARGET_EXP -60
28 | #define MASK32 0xFFFFFFFFULL
29 |
30 | #define CAST_U64(d) (*(uint64_t*)&d)
31 | #define MIN(x,y) ((x) <= (y) ? (x) : (y))
32 | #define MAX(x,y) ((x) >= (y) ? (x) : (y))
33 |
34 | #define MIN_CACHED_EXP -348
35 | #define CACHED_EXP_STEP 8
36 |
37 | typedef struct diy_fp
38 | {
39 | uint64_t f;
40 | int e;
41 | } diy_fp;
42 |
43 | typedef struct power
44 | {
45 | uint64_t fract;
46 | int16_t b_exp, d_exp;
47 | } power;
48 |
49 | static const power pow_cache[] =
50 | {
51 | { 0xfa8fd5a0081c0288ULL, -1220, -348 },
52 | { 0xbaaee17fa23ebf76ULL, -1193, -340 },
53 | { 0x8b16fb203055ac76ULL, -1166, -332 },
54 | { 0xcf42894a5dce35eaULL, -1140, -324 },
55 | { 0x9a6bb0aa55653b2dULL, -1113, -316 },
56 | { 0xe61acf033d1a45dfULL, -1087, -308 },
57 | { 0xab70fe17c79ac6caULL, -1060, -300 },
58 | { 0xff77b1fcbebcdc4fULL, -1034, -292 },
59 | { 0xbe5691ef416bd60cULL, -1007, -284 },
60 | { 0x8dd01fad907ffc3cULL, -980, -276 },
61 | { 0xd3515c2831559a83ULL, -954, -268 },
62 | { 0x9d71ac8fada6c9b5ULL, -927, -260 },
63 | { 0xea9c227723ee8bcbULL, -901, -252 },
64 | { 0xaecc49914078536dULL, -874, -244 },
65 | { 0x823c12795db6ce57ULL, -847, -236 },
66 | { 0xc21094364dfb5637ULL, -821, -228 },
67 | { 0x9096ea6f3848984fULL, -794, -220 },
68 | { 0xd77485cb25823ac7ULL, -768, -212 },
69 | { 0xa086cfcd97bf97f4ULL, -741, -204 },
70 | { 0xef340a98172aace5ULL, -715, -196 },
71 | { 0xb23867fb2a35b28eULL, -688, -188 },
72 | { 0x84c8d4dfd2c63f3bULL, -661, -180 },
73 | { 0xc5dd44271ad3cdbaULL, -635, -172 },
74 | { 0x936b9fcebb25c996ULL, -608, -164 },
75 | { 0xdbac6c247d62a584ULL, -582, -156 },
76 | { 0xa3ab66580d5fdaf6ULL, -555, -148 },
77 | { 0xf3e2f893dec3f126ULL, -529, -140 },
78 | { 0xb5b5ada8aaff80b8ULL, -502, -132 },
79 | { 0x87625f056c7c4a8bULL, -475, -124 },
80 | { 0xc9bcff6034c13053ULL, -449, -116 },
81 | { 0x964e858c91ba2655ULL, -422, -108 },
82 | { 0xdff9772470297ebdULL, -396, -100 },
83 | { 0xa6dfbd9fb8e5b88fULL, -369, -92 },
84 | { 0xf8a95fcf88747d94ULL, -343, -84 },
85 | { 0xb94470938fa89bcfULL, -316, -76 },
86 | { 0x8a08f0f8bf0f156bULL, -289, -68 },
87 | { 0xcdb02555653131b6ULL, -263, -60 },
88 | { 0x993fe2c6d07b7facULL, -236, -52 },
89 | { 0xe45c10c42a2b3b06ULL, -210, -44 },
90 | { 0xaa242499697392d3ULL, -183, -36 },
91 | { 0xfd87b5f28300ca0eULL, -157, -28 },
92 | { 0xbce5086492111aebULL, -130, -20 },
93 | { 0x8cbccc096f5088ccULL, -103, -12 },
94 | { 0xd1b71758e219652cULL, -77, -4 },
95 | { 0x9c40000000000000ULL, -50, 4 },
96 | { 0xe8d4a51000000000ULL, -24, 12 },
97 | { 0xad78ebc5ac620000ULL, 3, 20 },
98 | { 0x813f3978f8940984ULL, 30, 28 },
99 | { 0xc097ce7bc90715b3ULL, 56, 36 },
100 | { 0x8f7e32ce7bea5c70ULL, 83, 44 },
101 | { 0xd5d238a4abe98068ULL, 109, 52 },
102 | { 0x9f4f2726179a2245ULL, 136, 60 },
103 | { 0xed63a231d4c4fb27ULL, 162, 68 },
104 | { 0xb0de65388cc8ada8ULL, 189, 76 },
105 | { 0x83c7088e1aab65dbULL, 216, 84 },
106 | { 0xc45d1df942711d9aULL, 242, 92 },
107 | { 0x924d692ca61be758ULL, 269, 100 },
108 | { 0xda01ee641a708deaULL, 295, 108 },
109 | { 0xa26da3999aef774aULL, 322, 116 },
110 | { 0xf209787bb47d6b85ULL, 348, 124 },
111 | { 0xb454e4a179dd1877ULL, 375, 132 },
112 | { 0x865b86925b9bc5c2ULL, 402, 140 },
113 | { 0xc83553c5c8965d3dULL, 428, 148 },
114 | { 0x952ab45cfa97a0b3ULL, 455, 156 },
115 | { 0xde469fbd99a05fe3ULL, 481, 164 },
116 | { 0xa59bc234db398c25ULL, 508, 172 },
117 | { 0xf6c69a72a3989f5cULL, 534, 180 },
118 | { 0xb7dcbf5354e9beceULL, 561, 188 },
119 | { 0x88fcf317f22241e2ULL, 588, 196 },
120 | { 0xcc20ce9bd35c78a5ULL, 614, 204 },
121 | { 0x98165af37b2153dfULL, 641, 212 },
122 | { 0xe2a0b5dc971f303aULL, 667, 220 },
123 | { 0xa8d9d1535ce3b396ULL, 694, 228 },
124 | { 0xfb9b7cd9a4a7443cULL, 720, 236 },
125 | { 0xbb764c4ca7a44410ULL, 747, 244 },
126 | { 0x8bab8eefb6409c1aULL, 774, 252 },
127 | { 0xd01fef10a657842cULL, 800, 260 },
128 | { 0x9b10a4e5e9913129ULL, 827, 268 },
129 | { 0xe7109bfba19c0c9dULL, 853, 276 },
130 | { 0xac2820d9623bf429ULL, 880, 284 },
131 | { 0x80444b5e7aa7cf85ULL, 907, 292 },
132 | { 0xbf21e44003acdd2dULL, 933, 300 },
133 | { 0x8e679c2f5e44ff8fULL, 960, 308 },
134 | { 0xd433179d9c8cb841ULL, 986, 316 },
135 | { 0x9e19db92b4e31ba9ULL, 1013, 324 },
136 | { 0xeb96bf6ebadf77d9ULL, 1039, 332 },
137 | { 0xaf87023b9bf0ee6bULL, 1066, 340 }
138 | };
139 |
140 | static int cached_pow(int exp, diy_fp *p)
141 | {
142 | int k = (int)ceil((exp+DIYFP_FRACT_SIZE-1) * D_1_LOG2_10);
143 | int i = (k-MIN_CACHED_EXP-1) / CACHED_EXP_STEP + 1;
144 | p->f = pow_cache[i].fract;
145 | p->e = pow_cache[i].b_exp;
146 | return pow_cache[i].d_exp;
147 | }
148 |
149 | static diy_fp minus(diy_fp x, diy_fp y)
150 | {
151 | diy_fp d; d.f = x.f - y.f; d.e = x.e;
152 | assert(x.e == y.e && x.f >= y.f);
153 | return d;
154 | }
155 |
156 | static diy_fp multiply(diy_fp x, diy_fp y)
157 | {
158 | uint64_t a, b, c, d, ac, bc, ad, bd, tmp;
159 | diy_fp r;
160 | a = x.f >> 32; b = x.f & MASK32;
161 | c = y.f >> 32; d = y.f & MASK32;
162 | ac = a*c; bc = b*c;
163 | ad = a*d; bd = b*d;
164 | tmp = (bd >> 32) + (ad & MASK32) + (bc & MASK32);
165 | tmp += 1U << 31; // round
166 | r.f = ac + (ad >> 32) + (bc >> 32) + (tmp >> 32);
167 | r.e = x.e + y.e + 64;
168 | return r;
169 | }
170 |
171 | static diy_fp normalize_diy_fp(diy_fp n)
172 | {
173 | assert(n.f != 0);
174 | while(!(n.f & 0xFFC0000000000000ULL)) { n.f <<= 10; n.e -= 10; }
175 | while(!(n.f & D64_SIGN)) { n.f <<= 1; --n.e; }
176 | return n;
177 | }
178 |
179 | static diy_fp double2diy_fp(double d)
180 | {
181 | diy_fp fp;
182 | uint64_t u64 = CAST_U64(d);
183 | if (!(u64 & D64_EXP_MASK)) { fp.f = u64 & D64_FRACT_MASK; fp.e = 1 - D64_EXP_BIAS; }
184 | else { fp.f = (u64 & D64_FRACT_MASK) + D64_IMPLICIT_ONE; fp.e = (int)((u64 & D64_EXP_MASK) >> D64_EXP_POS) - D64_EXP_BIAS; }
185 | return fp;
186 | }
187 |
188 | // pow10_cache[i] = 10^(i-1)
189 | static const unsigned int pow10_cache[] = { 0, 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000 };
190 |
191 | static int largest_pow10(uint32_t n, int n_bits, uint32_t *power)
192 | {
193 | int guess = ((n_bits + 1) * 1233 >> 12) + 1/*skip first entry*/;
194 | if (n < pow10_cache[guess]) --guess; // We don't have any guarantees that 2^n_bits <= n.
195 | *power = pow10_cache[guess];
196 | return guess;
197 | }
198 |
199 | static int round_weed(char *buffer, int len, uint64_t wp_W, uint64_t delta, uint64_t rest, uint64_t ten_kappa, uint64_t ulp)
200 | {
201 | uint64_t wp_Wup = wp_W - ulp;
202 | uint64_t wp_Wdown = wp_W + ulp;
203 | while(rest < wp_Wup && delta - rest >= ten_kappa
204 | && (rest + ten_kappa < wp_Wup || wp_Wup - rest >= rest + ten_kappa - wp_Wup))
205 | {
206 | --buffer[len-1];
207 | rest += ten_kappa;
208 | }
209 | if (rest < wp_Wdown && delta - rest >= ten_kappa
210 | && (rest + ten_kappa < wp_Wdown || wp_Wdown - rest > rest + ten_kappa - wp_Wdown))
211 | return 0;
212 |
213 | return 2*ulp <= rest && rest <= delta - 4*ulp;
214 | }
215 |
216 | static int digit_gen(diy_fp low, diy_fp w, diy_fp high, char *buffer, int *length, int *kappa)
217 | {
218 | uint64_t unit = 1;
219 | diy_fp too_low = { low.f - unit, low.e };
220 | diy_fp too_high = { high.f + unit, high.e };
221 | diy_fp unsafe_interval = minus(too_high, too_low);
222 | diy_fp one = { 1ULL << -w.e, w.e };
223 | uint32_t p1 = (uint32_t)(too_high.f >> -one.e);
224 | uint64_t p2 = too_high.f & (one.f - 1);
225 | uint32_t div;
226 | *kappa = largest_pow10(p1, DIYFP_FRACT_SIZE + one.e, &div);
227 | *length = 0;
228 |
229 | while(*kappa > 0)
230 | {
231 | uint64_t rest;
232 | int digit = p1 / div;
233 | buffer[*length] = (char)('0' + digit);
234 | ++*length;
235 | p1 %= div;
236 | --*kappa;
237 | rest = ((uint64_t)p1 << -one.e) + p2;
238 | if (rest < unsafe_interval.f) return round_weed(buffer, *length, minus(too_high, w).f, unsafe_interval.f, rest, (uint64_t)div << -one.e, unit);
239 | div /= 10;
240 | }
241 |
242 | for(;;)
243 | {
244 | int digit;
245 | p2 *= 10;
246 | unit *= 10;
247 | unsafe_interval.f *= 10;
248 | // Integer division by one.
249 | digit = (int)(p2 >> -one.e);
250 | buffer[*length] = (char)('0' + digit);
251 | ++*length;
252 | p2 &= one.f - 1; // Modulo by one.
253 | --*kappa;
254 | if (p2 < unsafe_interval.f) return round_weed(buffer, *length, minus(too_high, w).f * unit, unsafe_interval.f, p2, one.f, unit);
255 | }
256 | }
257 |
258 | static int grisu3(double v, char *buffer, int *length, int *d_exp)
259 | {
260 | int mk, kappa, success;
261 | diy_fp dfp = double2diy_fp(v);
262 | diy_fp w = normalize_diy_fp(dfp);
263 |
264 | // normalize boundaries
265 | diy_fp t = { (dfp.f << 1) + 1, dfp.e - 1 };
266 | diy_fp b_plus = normalize_diy_fp(t);
267 | diy_fp b_minus;
268 | diy_fp c_mk; // Cached power of ten: 10^-k
269 | uint64_t u64 = CAST_U64(v);
270 | assert(v > 0 && v <= 1.7976931348623157e308); // Grisu only handles strictly positive finite numbers.
271 | if (!(u64 & D64_FRACT_MASK) && (u64 & D64_EXP_MASK) != 0) { b_minus.f = (dfp.f << 2) - 1; b_minus.e = dfp.e - 2;} // lower boundary is closer?
272 | else { b_minus.f = (dfp.f << 1) - 1; b_minus.e = dfp.e - 1; }
273 | b_minus.f = b_minus.f << (b_minus.e - b_plus.e);
274 | b_minus.e = b_plus.e;
275 |
276 | mk = cached_pow(MIN_TARGET_EXP - DIYFP_FRACT_SIZE - w.e, &c_mk);
277 |
278 | w = multiply(w, c_mk);
279 | b_minus = multiply(b_minus, c_mk);
280 | b_plus = multiply(b_plus, c_mk);
281 |
282 | success = digit_gen(b_minus, w, b_plus, buffer, length, &kappa);
283 | *d_exp = kappa - mk;
284 | return success;
285 | }
286 |
287 | static int i_to_str(int val, char *str)
288 | {
289 | int len, i;
290 | char *s;
291 | char *begin = str;
292 | if (val < 0) { *str++ = '-'; val = -val; }
293 | s = str;
294 |
295 | for(;;)
296 | {
297 | int ni = val / 10;
298 | int digit = val - ni*10;
299 | *s++ = (char)('0' + digit);
300 | if (ni == 0)
301 | break;
302 | val = ni;
303 | }
304 | *s = '\0';
305 | len = (int)(s - str);
306 | for(i = 0; i < len/2; ++i)
307 | {
308 | char ch = str[i];
309 | str[i] = str[len-1-i];
310 | str[len-1-i] = ch;
311 | }
312 |
313 | return (int)(s - begin);
314 | }
315 |
316 | int dtoa_grisu3(double v, char *dst)
317 | {
318 | int d_exp, len, success, decimals, i;
319 | uint64_t u64 = CAST_U64(v);
320 | char *s2 = dst;
321 | assert(dst);
322 |
323 | // Prehandle NaNs
324 | if ((u64 << 1) > 0xFFE0000000000000ULL) return sprintf(dst, "NaN(%08X%08X)", (uint32_t)(u64 >> 32), (uint32_t)u64);
325 | // Prehandle negative values.
326 | if ((u64 & D64_SIGN) != 0) { *s2++ = '-'; v = -v; u64 ^= D64_SIGN; }
327 | // Prehandle zero.
328 | if (!u64) { *s2++ = '0'; *s2 = '\0'; return (int)(s2 - dst); }
329 | // Prehandle infinity.
330 | if (u64 == D64_EXP_MASK) { *s2++ = 'i'; *s2++ = 'n'; *s2++ = 'f'; *s2 = '\0'; return (int)(s2 - dst); }
331 |
332 | success = grisu3(v, s2, &len, &d_exp);
333 | // If grisu3 was not able to convert the number to a string, then use old sprintf (suboptimal).
334 | if (!success) return sprintf(s2, "%.17g", v) + (int)(s2 - dst);
335 |
336 | // We now have an integer string of form "151324135" and a base-10 exponent for that number.
337 | // Next, decide the best presentation for that string by whether to use a decimal point, or the scientific exponent notation 'e'.
338 | // We don't pick the absolute shortest representation, but pick a balance between readability and shortness, e.g.
339 | // 1.545056189557677e-308 could be represented in a shorter form
340 | // 1545056189557677e-323 but that would be somewhat unreadable.
341 | decimals = MIN(-d_exp, MAX(1, len-1));
342 | if (d_exp < 0 && len > 1) // Add decimal point?
343 | {
344 | for(i = 0; i < decimals; ++i) s2[len-i] = s2[len-i-1];
345 | s2[len++ - decimals] = '.';
346 | d_exp += decimals;
347 | // Need scientific notation as well?
348 | if (d_exp != 0) { s2[len++] = 'e'; len += i_to_str(d_exp, s2+len); }
349 | }
350 | else if (d_exp < 0 && d_exp >= -3) // Add decimal point for numbers of form 0.000x where it's shorter?
351 | {
352 | for(i = 0; i < len; ++i) s2[len-d_exp-1-i] = s2[len-i-1];
353 | s2[0] = '.';
354 | for(i = 1; i < -d_exp; ++i) s2[i] = '0';
355 | len += -d_exp;
356 | }
357 | // Add scientific notation?
358 | else if (d_exp < 0 || d_exp > 2) { s2[len++] = 'e'; len += i_to_str(d_exp, s2+len); }
359 | // Add zeroes instead of scientific notation?
360 | else if (d_exp > 0) { while(d_exp-- > 0) s2[len++] = '0'; }
361 | s2[len] = '\0'; // grisu3 doesn't null terminate, so ensure termination.
362 | return (int)(s2+len-dst);
363 | }
--------------------------------------------------------------------------------
/src/spatial/grisu3.h:
--------------------------------------------------------------------------------
1 | /* This file originated from https://github.com/juj/MathGeoLib
2 | * License: http://www.apache.org/licenses/LICENSE-2.0.html */
3 | /* This file is part of an implementation of the "grisu3" double to string
4 | conversion algorithm described in the research paper
5 | "Printing Floating-Point Numbers Quickly And Accurately with Integers"
6 | by Florian Loitsch, available at
7 | http://www.cs.tufts.edu/~nr/cs257/archive/florian-loitsch/printf.pdf */
8 |
9 | #ifndef GRISU3_H_
10 | #define GRISU3_H_
11 |
12 | #if defined(__cplusplus)
13 | extern "C" {
14 | #endif
15 |
16 | /// Converts the given double-precision floating point number to a string representation.
17 | /** For most inputs, this string representation is the
18 | shortest such, which deserialized again, returns the same bit
19 | representation of the double.
20 | @param v The number to convert.
21 | @param dst [out] The double-precision floating point number will be written here
22 | as a null-terminated string. The conversion algorithm will write at most 25 bytes
23 | to this buffer. (null terminator is included in this count).
24 | The dst pointer may not be null.
25 | @return the number of characters written to dst, excluding the null terminator (which
26 | is always written) is returned here. */
27 | int dtoa_grisu3(double v, char *dst);
28 |
29 | #if defined(__cplusplus)
30 | }
31 | #endif
32 | #endif /* GEOM_H_ */
--------------------------------------------------------------------------------
/src/spatial/hash.c:
--------------------------------------------------------------------------------
1 | /* Geohash implementation ported from Javascript to C */
2 |
3 | /* Javascript copyright
4 | *
5 | * Geohash encoding/decoding and associated functions
6 | * (c) Chris Veness 2014 / MIT Licence
7 | */
8 |
9 | /* C copyright
10 | *
11 | * Copyright (c) 2016, Josh Baker .
12 | * All rights reserved.
13 | *
14 | * Redistribution and use in source and binary forms, with or without
15 | * modification, are permitted provided that the following conditions are met:
16 | *
17 | * * Redistributions of source code must retain the above copyright notice,
18 | * this list of conditions and the following disclaimer.
19 | * * Redistributions in binary form must reproduce the above copyright
20 | * notice, this list of conditions and the following disclaimer in the
21 | * documentation and/or other materials provided with the distribution.
22 | * * Neither the name of Redis nor the names of its contributors may be used
23 | * to endorse or promote products derived from this software without
24 | * specific prior written permission.
25 | *
26 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
27 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
28 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
29 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
30 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
31 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
32 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
33 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
34 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
35 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
36 | * THE POSSIBILITY OF SUCH DAMAGE.
37 | */
38 |
39 | #include "hash.h"
40 |
41 | static inline int base32R(char b);
42 | static inline char base32F(int i);
43 |
44 | int hashEncode(double lat, double lon, int precision, char *hash){
45 | int idx = 0; // index into base32 map
46 | int bit = 0; // each char holds 5 bits
47 | int evenBit = 1;
48 | int glen = 0;
49 | double latMin = -90.0;
50 | double latMax = 90.0;
51 | double lonMin = -180.0;
52 | double lonMax = 180.0;
53 | if (precision < 1 || !hash) {
54 | return 0;
55 | }
56 |
57 | while (glen < precision) {
58 | if (evenBit) {
59 | // bisect E-W longitude
60 | double lonMid = (lonMin + lonMax) / 2;
61 | if (lon > lonMid) {
62 | idx = idx*2 + 1;
63 | lonMin = lonMid;
64 | } else {
65 | idx = idx * 2;
66 | lonMax = lonMid;
67 | }
68 | } else {
69 | // bisect N-S latitude
70 | double latMid = (latMin + latMax) / 2;
71 | if (lat > latMid) {
72 | idx = idx*2 + 1;
73 | latMin = latMid;
74 | } else {
75 | idx = idx * 2;
76 | latMax = latMid;
77 | }
78 | }
79 | evenBit = evenBit?0:1;
80 |
81 | bit = bit + 1;
82 | if (bit == 5) {
83 | // 5 bits gives us a character: append it and start over
84 | char b = base32F(idx);
85 | if (!b) {
86 | return 0;
87 | }
88 | hash[glen++] = b;
89 | bit = 0;
90 | idx = 0;
91 | }
92 | }
93 | hash[glen] = '\0';
94 | return glen;
95 | }
96 |
97 | int hashBounds(char *hash, double *swLat, double *swLon, double *neLat, double *neLon){
98 | int evenBit = 1;
99 | double latMin = -90.0;
100 | double latMax = 90.0;
101 | double lonMin = -180.0;
102 | double lonMax = 180.0;
103 | char *p = hash;
104 | while (*p) {
105 | char chr = *p;
106 | int idx = base32R(chr);
107 | if (idx == -1) {
108 | return 0;
109 | }
110 | for (int n=4;n>=0;n--) {
111 | int bitN = idx >> n & 1;
112 | if (evenBit) {
113 | // longitude
114 | double lonMid = (lonMin + lonMax) / 2;
115 | if (bitN == 1) {
116 | lonMin = lonMid;
117 | } else {
118 | lonMax = lonMid;
119 | }
120 | } else {
121 | // latitude
122 | double latMid = (latMin + latMax) / 2;
123 | if (bitN == 1) {
124 | latMin = latMid;
125 | } else {
126 | latMax = latMid;
127 | }
128 | }
129 | evenBit = evenBit?0:1;
130 | if (n == 0) {
131 | break;
132 | }
133 | }
134 | p++;
135 | }
136 | if (swLat) *swLat = latMin;
137 | if (swLon) *swLon = lonMin;
138 | if (neLat) *neLat = latMax;
139 | if (neLon) *neLon = lonMax;
140 | return 1;
141 | }
142 |
143 | int hashDecode(char *hash, double *lat, double *lon) {
144 | if (!hash){
145 | return 0;
146 | }
147 | double swLat, swLon, neLat, neLon;
148 | if (!hashBounds(hash, &swLat, &swLon, &neLat, &neLon)){
149 | return 0;
150 | }
151 | if (lat) *lat = (neLat-swLat)/2 + swLat;
152 | if (lon) *lon = (neLon-swLon)/2 + swLon;
153 | return 1;
154 | }
155 |
156 | static inline int base32R(char b) {
157 | switch (b) {
158 | default:return -1;
159 | case '0':return 0;
160 | case '1':return 1;
161 | case '2':return 2;
162 | case '3':return 3;
163 | case '4':return 4;
164 | case '5':return 5;
165 | case '6':return 6;
166 | case '7':return 7;
167 | case '8':return 8;
168 | case '9':return 9;
169 | case 'b':case 'B':return 10;
170 | case 'c':case 'C':return 11;
171 | case 'd':case 'D':return 12;
172 | case 'e':case 'E':return 13;
173 | case 'f':case 'F':return 14;
174 | case 'g':case 'G':return 15;
175 | case 'h':case 'H':return 16;
176 | case 'j':case 'J':return 17;
177 | case 'k':case 'K':return 18;
178 | case 'm':case 'M':return 19;
179 | case 'n':case 'N':return 20;
180 | case 'p':case 'P':return 21;
181 | case 'q':case 'Q':return 22;
182 | case 'r':case 'R':return 23;
183 | case 's':case 'S':return 24;
184 | case 't':case 'T':return 25;
185 | case 'u':case 'U':return 26;
186 | case 'v':case 'V':return 27;
187 | case 'w':case 'W':return 28;
188 | case 'x':case 'X':return 29;
189 | case 'y':case 'Y':return 30;
190 | case 'z':case 'Z':return 31;
191 | }
192 | }
193 |
194 | static char base32[32] = "0123456789bcdefghjkmnpqrstuvwxyz";
195 |
196 | static inline char base32F(int i) {
197 | if (i<0||i>31){
198 | return 0;
199 | }
200 | return base32[i];
201 | }
202 |
203 |
204 |
--------------------------------------------------------------------------------
/src/spatial/hash.h:
--------------------------------------------------------------------------------
1 | /* Copyright (c) 2016, Josh Baker
2 | * All rights reserved.
3 | *
4 | * Redistribution and use in source and binary forms, with or without
5 | * modification, are permitted provided that the following conditions are met:
6 | *
7 | * * Redistributions of source code must retain the above copyright notice,
8 | * this list of conditions and the following disclaimer.
9 | * * Redistributions in binary form must reproduce the above copyright
10 | * notice, this list of conditions and the following disclaimer in the
11 | * documentation and/or other materials provided with the distribution.
12 | * * Neither the name of Redis nor the names of its contributors may be used
13 | * to endorse or promote products derived from this software without
14 | * specific prior written permission.
15 | *
16 | * THIS SOFTWARE IS PROVIDED by THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
20 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26 | * POSSIBILITY OF SUCH DAMAGE.
27 | */
28 |
29 | #ifndef HASH_H_
30 | #define HASH_H_
31 |
32 | #if defined(__cplusplus)
33 | extern "C" {
34 | #endif
35 |
36 | /* hashEncode creates a geohash from the provided lat,lon.
37 | * Make sure that the hash has enough room to fit the precision.
38 | * The return value is zero for error or the number of
39 | * bytes written to the hash string. */
40 | int hashEncode(double lat, double lon, int precision, char *hash);
41 |
42 | /* hashDecode decodes a geohash and puts the results into lat,lon.
43 | * The return value is zero for error or one for success.
44 | */
45 | int hashDecode(char *hash, double *lat, double *lon);
46 |
47 |
48 | /* hashBounds decodes a geohash into bounds and puts the results
49 | * into swlat,swlon,nelat,nelon.
50 | * The return value is zero for error or one for success.
51 | */
52 | int hashBounds(char *hash, double *swLat, double *swLon, double *neLat, double *neLon);
53 |
54 |
55 | #if defined(__cplusplus)
56 | }
57 | #endif
58 |
59 | #endif /* HASH_H_ */
60 |
--------------------------------------------------------------------------------
/src/spatial/json.h:
--------------------------------------------------------------------------------
1 |
2 | /* vim: set et ts=3 sw=3 sts=3 ft=c:
3 | *
4 | * Copyright (C) 2012, 2013, 2014 James McLaughlin et al. All rights reserved.
5 | * https://github.com/udp/json-parser
6 | *
7 | * Redistribution and use in source and binary forms, with or without
8 | * modification, are permitted provided that the following conditions
9 | * are met:
10 | *
11 | * 1. Redistributions of source code must retain the above copyright
12 | * notice, this list of conditions and the following disclaimer.
13 | *
14 | * 2. Redistributions in binary form must reproduce the above copyright
15 | * notice, this list of conditions and the following disclaimer in the
16 | * documentation and/or other materials provided with the distribution.
17 | *
18 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
19 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
22 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28 | * SUCH DAMAGE.
29 | */
30 |
31 | #ifndef _JSON_H
32 | #define _JSON_H
33 |
34 | #ifndef json_char
35 | #define json_char char
36 | #endif
37 |
38 | #ifndef json_int_t
39 | #ifndef _MSC_VER
40 | #include
41 | #define json_int_t int64_t
42 | #else
43 | #define json_int_t __int64
44 | #endif
45 | #endif
46 |
47 | #include
48 |
49 | #ifdef __cplusplus
50 |
51 | #include
52 |
53 | extern "C"
54 | {
55 |
56 | #endif
57 |
58 | typedef struct
59 | {
60 | unsigned long max_memory;
61 | int settings;
62 |
63 | /* Custom allocator support (leave null to use malloc/free)
64 | */
65 |
66 | void * (* mem_alloc) (size_t, int zero, void * user_data);
67 | void (* mem_free) (void *, void * user_data);
68 |
69 | void * user_data; /* will be passed to mem_alloc and mem_free */
70 |
71 | size_t value_extra; /* how much extra space to allocate for values? */
72 |
73 | } json_settings;
74 |
75 | #define json_enable_comments 0x01
76 |
77 | typedef enum
78 | {
79 | json_none,
80 | json_object,
81 | json_array,
82 | json_integer,
83 | json_double,
84 | json_string,
85 | json_boolean,
86 | json_null
87 |
88 | } json_type;
89 |
90 | extern const struct _json_value json_value_none;
91 |
92 | typedef struct _json_object_entry
93 | {
94 | json_char * name;
95 | unsigned int name_length;
96 |
97 | struct _json_value * value;
98 |
99 | } json_object_entry;
100 |
101 | typedef struct _json_value
102 | {
103 | struct _json_value * parent;
104 |
105 | json_type type;
106 |
107 | union
108 | {
109 | int boolean;
110 | json_int_t integer;
111 | double dbl;
112 |
113 | struct
114 | {
115 | unsigned int length;
116 | json_char * ptr; /* null terminated */
117 |
118 | } string;
119 |
120 | struct
121 | {
122 | unsigned int length;
123 |
124 | json_object_entry * values;
125 |
126 | #if defined(__cplusplus) && __cplusplus >= 201103L
127 | decltype(values) begin () const
128 | { return values;
129 | }
130 | decltype(values) end () const
131 | { return values + length;
132 | }
133 | #endif
134 |
135 | } object;
136 |
137 | struct
138 | {
139 | unsigned int length;
140 | struct _json_value ** values;
141 |
142 | #if defined(__cplusplus) && __cplusplus >= 201103L
143 | decltype(values) begin () const
144 | { return values;
145 | }
146 | decltype(values) end () const
147 | { return values + length;
148 | }
149 | #endif
150 |
151 | } array;
152 |
153 | } u;
154 |
155 | union
156 | {
157 | struct _json_value * next_alloc;
158 | void * object_mem;
159 |
160 | } _reserved;
161 |
162 | #ifdef JSON_TRACK_SOURCE
163 |
164 | /* Location of the value in the source JSON
165 | */
166 | unsigned int line, col;
167 |
168 | #endif
169 |
170 |
171 | /* Some C++ operator sugar */
172 |
173 | #ifdef __cplusplus
174 |
175 | public:
176 |
177 | inline _json_value ()
178 | { memset (this, 0, sizeof (_json_value));
179 | }
180 |
181 | inline const struct _json_value &operator [] (int index) const
182 | {
183 | if (type != json_array || index < 0
184 | || ((unsigned int) index) >= u.array.length)
185 | {
186 | return json_value_none;
187 | }
188 |
189 | return *u.array.values [index];
190 | }
191 |
192 | inline const struct _json_value &operator [] (const char * index) const
193 | {
194 | if (type != json_object)
195 | return json_value_none;
196 |
197 | for (unsigned int i = 0; i < u.object.length; ++ i)
198 | if (!strcmp (u.object.values [i].name, index))
199 | return *u.object.values [i].value;
200 |
201 | return json_value_none;
202 | }
203 |
204 | inline operator const char * () const
205 | {
206 | switch (type)
207 | {
208 | case json_string:
209 | return u.string.ptr;
210 |
211 | default:
212 | return "";
213 | };
214 | }
215 |
216 | inline operator json_int_t () const
217 | {
218 | switch (type)
219 | {
220 | case json_integer:
221 | return u.integer;
222 |
223 | case json_double:
224 | return (json_int_t) u.dbl;
225 |
226 | default:
227 | return 0;
228 | };
229 | }
230 |
231 | inline operator bool () const
232 | {
233 | if (type != json_boolean)
234 | return false;
235 |
236 | return u.boolean != 0;
237 | }
238 |
239 | inline operator double () const
240 | {
241 | switch (type)
242 | {
243 | case json_integer:
244 | return (double) u.integer;
245 |
246 | case json_double:
247 | return u.dbl;
248 |
249 | default:
250 | return 0;
251 | };
252 | }
253 |
254 | #endif
255 |
256 | } json_value;
257 |
258 | json_value * json_parse (const json_char * json,
259 | size_t length);
260 |
261 | #define json_error_max 128
262 | json_value * json_parse_ex (json_settings * settings,
263 | const json_char * json,
264 | size_t length,
265 | char * error);
266 |
267 | void json_value_free (json_value *);
268 |
269 |
270 | /* Not usually necessary, unless you used a custom mem_alloc and now want to
271 | * use a custom mem_free.
272 | */
273 | void json_value_free_ex (json_settings * settings,
274 | json_value *);
275 |
276 |
277 | #ifdef __cplusplus
278 | } /* extern "C" */
279 | #endif
280 |
281 | #endif
282 |
283 |
--------------------------------------------------------------------------------
/src/spatial/poly.c:
--------------------------------------------------------------------------------
1 | /* Copyright (c) 2016, Josh Baker
2 | * All rights reserved.
3 | *
4 | * Redistribution and use in source and binary forms, with or without
5 | * modification, are permitted provided that the following conditions are met:
6 | *
7 | * * Redistributions of source code must retain the above copyright notice,
8 | * this list of conditions and the following disclaimer.
9 | * * Redistributions in binary form must reproduce the above copyright
10 | * notice, this list of conditions and the following disclaimer in the
11 | * documentation and/or other materials provided with the distribution.
12 | * * Neither the name of Redis nor the names of its contributors may be used
13 | * to endorse or promote products derived from this software without
14 | * specific prior written permission.
15 | *
16 | * THIS SOFTWARE IS PROVIDED by THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
20 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26 | * POSSIBILITY OF SUCH DAMAGE.
27 | */
28 |
29 | #include
30 | #include
31 | #include
32 | #include
33 | #include "zmalloc.h"
34 | #include "poly.h"
35 | #include "grisu3.h"
36 |
37 | // InsideRect detects point is inside of another rect
38 | int polyPointInsideRect(polyPoint p, polyRect r) {
39 | if (p.x < r.min.x || p.x > r.max.x) {
40 | return 0;
41 | }
42 | if (p.y < r.min.y || p.y > r.max.y) {
43 | return 0;
44 | }
45 | return 1;
46 | }
47 |
48 | polyPolygon polyPolygonFromGeomSegment(void *segment, int dims){
49 | polyPolygon pp;
50 | pp.len = (int)((uint32_t*)segment)[0];
51 | pp.dims = dims;
52 | pp.values = (double*)(((uint8_t*)segment)+4);
53 | return pp;
54 | }
55 |
56 | polyPoint polyPolygonPoint(polyPolygon pp, int idx){
57 | polyPoint p = {
58 | pp.values[idx*pp.dims+0],
59 | pp.values[idx*pp.dims+1],
60 | };
61 | return p;
62 | }
63 |
64 | polyMultiPolygon polyMultiPolygonFromGeomSegment(void *segment, int dims){
65 | polyMultiPolygon mp;
66 | mp.len = (int)((uint32_t*)segment)[0];
67 | mp.dims = dims;
68 | mp.values = (((uint8_t*)segment)+4);
69 | return mp;
70 | }
71 |
72 | polyPolygon polyMultiPolygonPolygon(polyMultiPolygon mp, int idx){
73 | uint8_t *segment = mp.values;
74 | for (int i=0;i bbox.max.x) {
106 | bbox.max.x = p.x;
107 | }
108 | if (p.y < bbox.min.y) {
109 | bbox.min.y = p.y;
110 | } else if (p.y > bbox.max.y) {
111 | bbox.max.y = p.y;
112 | }
113 | }
114 | }
115 | return bbox;
116 | }
117 |
118 | // IntersectsRect detects if two bboxes intersect.
119 | int polyRectIntersectsRect(polyRect r, polyRect rect) {
120 | if (r.min.y > rect.max.y || r.max.y < rect.min.y) {
121 | return 0;
122 | }
123 | if (r.min.x > rect.max.x || r.max.x < rect.min.x) {
124 | return 0;
125 | }
126 | return 1;
127 | }
128 |
129 | // InsideRect detects rect is inside of another rect
130 | int polyRectInsideRect(polyRect r, polyRect rect) {
131 | if (r.min.x < rect.min.x || r.max.x > rect.max.x) {
132 | return 0;
133 | }
134 | if (r.min.y < rect.min.y || r.max.y > rect.max.y) {
135 | return 0;
136 | }
137 | return 1;
138 | }
139 |
140 | // String returns a string representation of the polygon.
141 | char *polyPolygonString(polyPolygon pp){
142 | int len = 0;
143 | char *str = NULL;
144 | str = zmalloc(10);
145 | if (!str){
146 | return NULL;
147 | }
148 | str[len++] = '{';
149 | for (int i=0;i 0){
152 | str[len++] = ',';
153 | str[len++] = ' ';
154 | }
155 | char buf[250];
156 | char dx[26];
157 | char dy[26];
158 | dtoa_grisu3(p.x, dx);
159 | dtoa_grisu3(p.y, dy);
160 | sprintf(buf, "{%s, %s}", dx, dy);
161 | char *nstr = zrealloc(str, len+strlen(buf)+10);
162 | if (!nstr){
163 | zfree(nstr);
164 | return NULL;
165 | }
166 | str = nstr;
167 | str[len] = '\0';
168 | strcat(str+len, buf);
169 | len += strlen(buf);
170 | }
171 | str[len++] = '}';
172 | str[len++] = '\0';
173 | return str;
174 | }
175 |
--------------------------------------------------------------------------------
/src/spatial/poly.h:
--------------------------------------------------------------------------------
1 | /* Copyright (c) 2016, Josh Baker
2 | * All rights reserved.
3 | *
4 | * Redistribution and use in source and binary forms, with or without
5 | * modification, are permitted provided that the following conditions are met:
6 | *
7 | * * Redistributions of source code must retain the above copyright notice,
8 | * this list of conditions and the following disclaimer.
9 | * * Redistributions in binary form must reproduce the above copyright
10 | * notice, this list of conditions and the following disclaimer in the
11 | * documentation and/or other materials provided with the distribution.
12 | * * Neither the name of Redis nor the names of its contributors may be used
13 | * to endorse or promote products derived from this software without
14 | * specific prior written permission.
15 | *
16 | * THIS SOFTWARE IS PROVIDED by THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
20 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26 | * POSSIBILITY OF SUCH DAMAGE.
27 | */
28 |
29 | #ifndef POLY_H_
30 | #define POLY_H_
31 |
32 | #if defined(__cplusplus)
33 | extern "C" {
34 | #endif
35 |
36 | typedef struct polyPoint {
37 | double x, y;
38 | } polyPoint;
39 |
40 | // Rect is rectangle
41 | typedef struct polyRect {
42 | polyPoint min, max;
43 | } polyRect;
44 |
45 |
46 | typedef enum polyRayres{
47 | RAY_OUT = 0, // outside of the segment.
48 | RAY_LEFT = 1, // to the left of the segment
49 | RAY_ON = 2, // on segment or vertex, special condition
50 | } polyRayres;
51 |
52 | typedef struct polyPolygon {
53 | int len; // number of points.
54 | int dims; // number of dimensions in polygon.
55 | double *values; // point values. this array will be len*dims in size.
56 | } polyPolygon;
57 |
58 | typedef struct polyMultiPolygon {
59 | int len; // number of polygons.
60 | int dims; // number of dimensions in polygon.
61 | void *values; // pointer to the first polygon.
62 | } polyMultiPolygon;
63 |
64 |
65 | // create a polygon from a geom segment. the segment should be composed of a
66 | // 32-bit unsigned int with represents the number of points in the polygon,
67 | // and a series of doubles. The dims value should be 2,3,4 depending on how
68 | // many doubles are required to make up a single point.
69 | polyPolygon polyPolygonFromGeomSegment(void *segment, int dims);
70 |
71 | // same as above, but for multiple polygons
72 | polyMultiPolygon polyMultiPolygonFromGeomSegment(void *segment, int dims);
73 |
74 | const char *polyRayresString(polyRayres r);
75 | polyRayres polyRaycast(polyPoint p, polyPoint a, polyPoint b);
76 | int polyPointInsideRect(polyPoint p, polyRect r);
77 | int polyPolygonInsideRect(polyPolygon pp, polyRect r);
78 | polyPoint polyPolygonPoint(polyPolygon pp, int idx);
79 | polyPolygon polyMultiPolygonPolygon(polyMultiPolygon mp, int idx);
80 | int polyPointInside(polyPoint p, polyPolygon exterior, polyMultiPolygon holes);
81 | int polyPolygonInside(polyPolygon shape, polyPolygon exterior, polyMultiPolygon holes);
82 | polyRect polyPolygonRect(polyPolygon pp);
83 | int polyRectIntersectsRect(polyRect r, polyRect rect);
84 | int polyRectInsideRect(polyRect r, polyRect rect);
85 | char *polyPolygonString(polyPolygon pp);
86 | int polyPointIntersects(polyPoint p, polyPolygon exterior, polyMultiPolygon holes);
87 | int polyPolygonIntersects(polyPolygon shape, polyPolygon exterior, polyMultiPolygon holes);
88 | int polyPolygonContains(polyPolygon shape, polyPolygon exterior, polyMultiPolygon holes);
89 | int polyLinesIntersect(polyPoint a1, polyPoint a2, polyPoint b1, polyPoint b2);
90 |
91 | polyPoint lineCenter(polyPoint a, polyPoint b);
92 | int polyLineIntersect(polyPoint a, polyPoint b, polyPolygon exterior, polyMultiPolygon holes);
93 | int polyLineInside(polyPoint a, polyPoint b, polyPolygon exterior, polyMultiPolygon holes);
94 | int lineintersects(polyPoint a, polyPoint b, polyPoint c, polyPoint d);
95 |
96 | #if defined(__cplusplus)
97 | }
98 | #endif
99 |
100 | #endif /* POLY_H_ */
101 |
--------------------------------------------------------------------------------
/src/spatial/poly_test.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include "poly.h"
7 | #include "test.h"
8 |
9 | polyPoint P(double x, double y);
10 | polyRect RECT(polyPoint min, polyPoint max){
11 | polyRect r = {min, max};
12 | return r;
13 | }
14 |
15 | int test_PolyRectIntersects() {
16 | assert(polyRectIntersectsRect(RECT(P(0, 0), P(10, 10)), RECT(P(-1, -1), P(1, 1))));
17 | assert(polyRectIntersectsRect(RECT(P(0, 0), P(10, 10)), RECT(P(9, 9), P(11, 11))));
18 | assert(polyRectIntersectsRect(RECT(P(0, 0), P(10, 10)), RECT(P(9, -1), P(11, 1))));
19 | assert(polyRectIntersectsRect(RECT(P(0, 0), P(10, 10)), RECT(P(-1, 9), P(1, 11))));
20 | assert(polyRectIntersectsRect(RECT(P(0, 0), P(10, 10)), RECT(P(-1, -1), P(0, 0))));
21 | assert(polyRectIntersectsRect(RECT(P(0, 0), P(10, 10)), RECT(P(10, 10), P(11, 11))));
22 | assert(polyRectIntersectsRect(RECT(P(0, 0), P(10, 10)), RECT(P(10, -1), P(11, 0))));
23 | assert(polyRectIntersectsRect(RECT(P(0, 0), P(10, 10)), RECT(P(-1, 10), P(0, 11))));
24 | assert(polyRectIntersectsRect(RECT(P(0, 0), P(10, 10)), RECT(P(1, 1), P(2, 2))));
25 | return 1;
26 | }
27 |
28 | int test_PolyRectInside() {
29 | assert(polyRectInsideRect(RECT(P(1, 1), P(9, 9)), RECT(P(0, 0), P(10, 10))));
30 | assert(!polyRectInsideRect(RECT(P(-1, -1), P(9, 9)), RECT(P(0, 0), P(10, 10))));
31 |
32 | return 1;
33 | }
--------------------------------------------------------------------------------
/src/spatial/polyinside.c:
--------------------------------------------------------------------------------
1 | /* Copyright (c) 2016, Josh Baker
2 | * All rights reserved.
3 | *
4 | * Redistribution and use in source and binary forms, with or without
5 | * modification, are permitted provided that the following conditions are met:
6 | *
7 | * * Redistributions of source code must retain the above copyright notice,
8 | * this list of conditions and the following disclaimer.
9 | * * Redistributions in binary form must reproduce the above copyright
10 | * notice, this list of conditions and the following disclaimer in the
11 | * documentation and/or other materials provided with the distribution.
12 | * * Neither the name of Redis nor the names of its contributors may be used
13 | * to endorse or promote products derived from this software without
14 | * specific prior written permission.
15 | *
16 | * THIS SOFTWARE IS PROVIDED by THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
20 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26 | * POSSIBILITY OF SUCH DAMAGE.
27 | */
28 |
29 | #include
30 | #include
31 | #include "poly.h"
32 |
33 | static polyMultiPolygon emptyMultiPolygon = {0,0,0};
34 |
35 | int insideshpext(polyPoint p, polyPolygon shape, int exterior) {
36 | // if len(shape) < 3 {
37 | // return false
38 | // }
39 | int in = 0;
40 | for (int i = 0; i < shape.len; i++) {
41 | polyPoint a = polyPolygonPoint(shape, i);
42 | polyPoint b = polyPolygonPoint(shape, (i+1)%shape.len);
43 | polyRayres res = polyRaycast(p, a, b);
44 | if (res == RAY_ON) {
45 | return exterior;
46 | }
47 |
48 | if (res == RAY_LEFT) {
49 | in = !in;
50 | }
51 | }
52 | return in;
53 | }
54 |
55 |
56 | // Inside returns true if point is inside of exterior and not in a hole.
57 | // The validity of the exterior and holes must be done elsewhere and are assumed valid.
58 | // A valid exterior is a near-linear ring.
59 | // A valid hole is one that is full contained inside the exterior.
60 | // A valid hole may not share the same segment line as the exterior.
61 | int polyPointInside(polyPoint p, polyPolygon exterior, polyMultiPolygon holes) {
62 | if (!insideshpext(p, exterior, 1)) {
63 | return 0;
64 | }
65 | for (int i=0;i
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include "zmalloc.h"
7 | #include "poly.h"
8 | #include "test.h"
9 |
10 | static polyMultiPolygon emptyMultiPolygon = {0,0,0};
11 |
12 | int insideshpext(polyPoint p, polyPolygon shape, int exterior);
13 |
14 | polyPoint P(double x, double y){
15 | polyPoint p = {x, y};
16 | return p;
17 | }
18 |
19 |
20 |
21 | void *makeSegment(int count, ...){
22 | void *segment = zmalloc(4+count*16);
23 | assert(segment);
24 | ((uint32_t*)segment)[0] = count;
25 | va_list ap;
26 | va_start(ap, count);
27 | for (int i=0;i
2 | * All rights reserved.
3 | *
4 | * Redistribution and use in source and binary forms, with or without
5 | * modification, are permitted provided that the following conditions are met:
6 | *
7 | * * Redistributions of source code must retain the above copyright notice,
8 | * this list of conditions and the following disclaimer.
9 | * * Redistributions in binary form must reproduce the above copyright
10 | * notice, this list of conditions and the following disclaimer in the
11 | * documentation and/or other materials provided with the distribution.
12 | * * Neither the name of Redis nor the names of its contributors may be used
13 | * to endorse or promote products derived from this software without
14 | * specific prior written permission.
15 | *
16 | * THIS SOFTWARE IS PROVIDED by THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
20 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26 | * POSSIBILITY OF SUCH DAMAGE.
27 | */
28 |
29 | #include
30 | #include "poly.h"
31 |
32 | static polyMultiPolygon emptyMultiPolygon = {0,0,0};
33 |
34 | polyPoint lineCenter(polyPoint a, polyPoint b){
35 | polyPoint p={
36 | (a.x+b.x)/2, (a.y+b.y)/2
37 | };
38 |
39 | return p;
40 | }
41 |
42 | int lineintersects(
43 | polyPoint a, polyPoint b, // segment 1
44 | polyPoint c, polyPoint d // segment 2
45 | ){
46 | // do the bounding boxes intersect?
47 | // the following checks without swapping values.
48 | if (a.y > b.y) {
49 | if (c.y > d.y) {
50 | if (b.y > c.y || a.y < d.y) {
51 | return 0;
52 | }
53 | } else {
54 | if (b.y > d.y || a.y < c.y) {
55 | return 0;
56 | }
57 | }
58 | } else {
59 | if (c.y > d.y) {
60 | if (a.y > c.y || b.y < d.y) {
61 | return 0;
62 | }
63 | } else {
64 | if (a.y > d.y || b.y < c.y) {
65 | return 0;
66 | }
67 | }
68 | }
69 | if (a.x > b.x) {
70 | if (c.x > d.x) {
71 | if (b.x > c.x || a.x < d.x) {
72 | return 0;
73 | }
74 | } else {
75 | if (b.x > d.x || a.x < c.x) {
76 | return 0;
77 | }
78 | }
79 | } else {
80 | if (c.x > d.x) {
81 | if (a.x > c.x || b.x < d.x) {
82 | return 0;
83 | }
84 | } else {
85 | if (a.x > d.x || b.x < c.x) {
86 | return 0;
87 | }
88 | }
89 | }
90 |
91 | // the following code is from http://ideone.com/PnPJgb
92 | double cmpx = c.x-a.x;
93 | double cmpy = c.y-a.y;
94 | double rx = b.x-a.x;
95 | double ry = b.y-a.y;
96 | double cmpxr = cmpx*ry - cmpy*rx;
97 | if (cmpxr == 0) {
98 | // Lines are collinear, and so intersect if they have any overlap
99 | if (!(((c.x-a.x <= 0) != (c.x-b.x <= 0)) || ((c.y-a.y <= 0) != (c.y-b.y <= 0)))) {
100 | return 0;
101 | }
102 | return 1;
103 | }
104 | double sx = d.x-c.x;
105 | double sy = d.y-c.y;
106 | double cmpxs = cmpx*sy - cmpy*sx;
107 | double rxs = rx*sy - ry*sx;
108 | if (rxs == 0) {
109 | return 0; // Lines are parallel.
110 | }
111 | double rxsr = 1 / rxs;
112 | double t = cmpxs * rxsr;
113 | double u = cmpxr * rxsr;
114 | if (!((t >= 0) && (t <= 1) && (u >= 0) && (u <= 1))) {
115 | return 0;
116 | }
117 | return 1;
118 | }
119 |
120 | // Intersects detects if a point intersects another polygon
121 | int polyPointIntersects(polyPoint p, polyPolygon exterior, polyMultiPolygon holes){
122 | return polyPointInside(p, exterior, holes);
123 | }
124 |
125 | int polyLinesIntersect(polyPoint a1, polyPoint a2, polyPoint b1, polyPoint b2){
126 | return lineintersects(a1,a2,b1,b2);
127 | }
128 |
129 | // Intersects detects if a polygon intersects another polygon
130 | int polyPolygonIntersects(polyPolygon shape, polyPolygon exterior, polyMultiPolygon holes) {
131 | switch (shape.len) {
132 | case 0:
133 | return 0;
134 | case 1:
135 | switch (exterior.len) {
136 | case 0:
137 | return 0;
138 | case 1:{
139 | polyPoint shape0 = polyPolygonPoint(shape, 0);
140 | polyPoint exterior0 = polyPolygonPoint(exterior, 0);
141 | return shape0.x == exterior0.x && shape0.y == shape0.y;
142 | }
143 | default:
144 | return polyPointInside(polyPolygonPoint(shape, 0), exterior, holes);
145 | }
146 | default:
147 | switch (exterior.len) {
148 | case 0:
149 | return 0;
150 | case 1:
151 | return polyPointInside(polyPolygonPoint(exterior, 0), shape, holes);
152 | }
153 | }
154 | if (!polyRectIntersectsRect(polyPolygonRect(shape), polyPolygonRect(exterior))) {
155 | return 0;
156 | }
157 | for (int i=0;i
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include "poly.h"
7 | #include "test.h"
8 |
9 | static polyMultiPolygon emptyMultiPolygon = {0,0,0};
10 |
11 | polyPoint P(double x, double y);
12 | void *makeSegment(int count, ...);
13 | void *makeMultiSegment(int count, ...);
14 |
15 | #define POLYGON(count, ...) (polyPolygonFromGeomSegment(makeSegment(count, __VA_ARGS__), 2))
16 | #define MULTIPOLYGON(count, ...) (polyMultiPolygonFromGeomSegment(makeMultiSegment(count, __VA_ARGS__), 2))
17 |
18 | int lineintersects(
19 | polyPoint a, polyPoint b, // segment 1
20 | polyPoint c, polyPoint d // segment 2
21 | );
22 |
23 | void testIntersectsLinesA(polyPoint a, polyPoint b, polyPoint c, polyPoint d, int expect) {
24 | int res = lineintersects(a, b, c, d);
25 | assert(res == expect);
26 | res = lineintersects(b, a, c, d);
27 | assert(res == expect);
28 | res = lineintersects(a, b, d, c);
29 | assert(res == expect);
30 | res = lineintersects(b, a, d, c);
31 | assert(res == expect);
32 | }
33 |
34 | void testIntersectsLines(polyPoint a, polyPoint b, polyPoint c, polyPoint d, int expect) {
35 | testIntersectsLinesA(a, b, c, d, expect);
36 | testIntersectsLinesA(c, d, a, b, expect);
37 | }
38 |
39 | int test_PolyIntersectsLines() {
40 | testIntersectsLines(P(0, 6), P(12, -6), P(0, 0), P(12, 0), 1);
41 | testIntersectsLines(P(0, 0), P(5, 5), P(5, 5), P(0, 10), 1);
42 | testIntersectsLines(P(0, 0), P(5, 5), P(5, 6), P(0, 10), 0);
43 | testIntersectsLines(P(0, 0), P(5, 5), P(5, 4), P(0, 10), 1);
44 | testIntersectsLines(P(0, 0), P(2, 2), P(0, 2), P(2, 0), 1);
45 | testIntersectsLines(P(0, 0), P(2, 2), P(0, 2), P(1, 1), 1);
46 | testIntersectsLines(P(0, 0), P(2, 2), P(2, 0), P(1, 1), 1);
47 | testIntersectsLines(P(0, 0), P(0, 4), P(1, 4), P(4, 1), 0);
48 | testIntersectsLines(P(0, 0), P(0, 4), P(1, 4), P(4, 4), 0);
49 | testIntersectsLines(P(0, 0), P(0, 4), P(4, 1), P(4, 4), 0);
50 | testIntersectsLines(P(0, 0), P(4, 0), P(1, 4), P(4, 1), 0);
51 | testIntersectsLines(P(0, 0), P(4, 0), P(1, 4), P(4, 4), 0);
52 | testIntersectsLines(P(0, 0), P(4, 0), P(4, 1), P(4, 4), 0);
53 | testIntersectsLines(P(0, 4), P(4, 0), P(1, 4), P(4, 1), 0);
54 | testIntersectsLines(P(0, 4), P(4, 0), P(1, 4), P(4, 4), 0);
55 | testIntersectsLines(P(0, 4), P(4, 0), P(4, 1), P(4, 4), 0);
56 | return 1;
57 | }
58 |
59 | void testIntersectsShapes(polyPolygon exterior, polyMultiPolygon holes, polyPolygon shape, int expect) {
60 | int got = polyPolygonIntersects(shape, exterior, holes);
61 | assert(got == expect);
62 | got = polyPolygonIntersects(exterior, shape, emptyMultiPolygon);
63 | assert(got == expect);
64 | }
65 |
66 |
67 | int test_PolyIntersectsShapes() {
68 | testIntersectsShapes(
69 | POLYGON(4, P(6, 0), P(12, 0), P(12, -6), P(6, 0)),
70 | emptyMultiPolygon,
71 | POLYGON(4, P(0, 0), P(0, 6), P(6, 0), P(0, 0)),
72 | 1);
73 |
74 | testIntersectsShapes(
75 | POLYGON(4, P(7, 0), P(12, 0), P(12, -6), P(7, 0)),
76 | emptyMultiPolygon,
77 | POLYGON(4, P(0, 0), P(0, 6), P(6, 0), P(0, 0)),
78 | 0);
79 |
80 | testIntersectsShapes(
81 | POLYGON(4, P(0.5, 0.5), P(0.5, 4.5), P(4.5, 0.5), P(0.5, 0.5)),
82 | emptyMultiPolygon,
83 | POLYGON(4, P(0, 0), P(0, 6), P(6, 0), P(0, 0)),
84 | 1);
85 |
86 | testIntersectsShapes(
87 | POLYGON(4, P(0, 0), P(0, 6), P(6, 0), P(0, 0)),
88 | MULTIPOLYGON(1, POLYGON(5, P(1, 1), P(1, 2), P(2, 2), P(2, 1), P(1, 1))),
89 | POLYGON(4, P(0.5, 0.5), P(0.5, 4.5), P(4.5, 0.5), P(0.5, 0.5)),
90 | 1);
91 |
92 | testIntersectsShapes(
93 | POLYGON(5, P(0, 0), P(0, 10), P(10, 10), P(10, 0), P(0, 0)),
94 | MULTIPOLYGON(1, POLYGON(5, P(2, 2), P(2, 6), P(6, 6), P(6, 2), P(2, 2))),
95 | POLYGON(5, P(1, 1), P(1, 9), P(9, 9), P(9, 1), P(1, 1)),
96 | 1);
97 | return 1;
98 | }
99 |
--------------------------------------------------------------------------------
/src/spatial/polyraycast.c:
--------------------------------------------------------------------------------
1 | /* Copyright (c) 2016, Josh Baker
2 | * All rights reserved.
3 | *
4 | * Redistribution and use in source and binary forms, with or without
5 | * modification, are permitted provided that the following conditions are met:
6 | *
7 | * * Redistributions of source code must retain the above copyright notice,
8 | * this list of conditions and the following disclaimer.
9 | * * Redistributions in binary form must reproduce the above copyright
10 | * notice, this list of conditions and the following disclaimer in the
11 | * documentation and/or other materials provided with the distribution.
12 | * * Neither the name of Redis nor the names of its contributors may be used
13 | * to endorse or promote products derived from this software without
14 | * specific prior written permission.
15 | *
16 | * THIS SOFTWARE IS PROVIDED by THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
20 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26 | * POSSIBILITY OF SUCH DAMAGE.
27 | */
28 |
29 | // This implementation of the raycast algorithm test if (a point is
30 | // to the left of a line, or on the segment line. Otherwise it is
31 | // assumed that the point is outside of the segment line.
32 |
33 | #include "poly.h"
34 |
35 | const char *polyRayresString(polyRayres r){
36 | switch (r) {
37 | default:
38 | return "unknown";
39 | case RAY_OUT:
40 | return "out";
41 | case RAY_LEFT:
42 | return "left";
43 | case RAY_ON:
44 | return "on";
45 | }
46 | }
47 |
48 | polyRayres polyRaycast(polyPoint p, polyPoint a, polyPoint b) {
49 | // Algorithm 1, There may be a potential bug in it.
50 | /* if (a.y == b.y) {
51 | // A and B share the same Y plane.
52 | if (a.x == b.x) {
53 | // AB is just a point.
54 | if (p.x == a.x && p.y == a.y) {
55 | return RAY_ON;
56 | }
57 | return RAY_OUT;
58 | }
59 | // AB is a horizontal line.
60 | if (p.y != a.y) {
61 | // P is not on same Y plane as A and B.
62 | return RAY_OUT;
63 | }
64 | // P is on same Y plane as A and B
65 | if (a.x < b.x) {
66 | if (p.x >= a.x && p.x <= b.x) {
67 | return RAY_ON;
68 | }
69 | if (p.x < a.x) {
70 | return RAY_LEFT;
71 | }
72 | } else {
73 | if (p.x >= b.x && p.x <= a.x) {
74 | return RAY_ON;
75 | }
76 | if (p.x < b.x) {
77 | return RAY_LEFT;
78 | }
79 | }
80 | return RAY_OUT;
81 | }
82 | if (a.x == b.x) {
83 | // AB is a vertical line.
84 | if (a.y > b.y) {
85 | // A is above B
86 | if (p.y > a.y || p.y < b.y) {
87 | return RAY_OUT;
88 | }
89 | } else {
90 | // B is above A
91 | if (p.y > b.y || p.y < a.y) {
92 | return RAY_OUT;
93 | }
94 | }
95 | if (p.x == a.x) {
96 | return RAY_ON;
97 | }
98 | if (p.x < a.x) {
99 | return RAY_LEFT;
100 | }
101 | return RAY_OUT;
102 | }
103 |
104 | // AB is an angled line
105 | if (a.y > b.y) {
106 | // swap A and B so that A is below B.
107 | double tx = a.x;
108 | double ty = a.y;
109 | a.x = b.x;
110 | a.y = b.y;
111 | b.x = tx;
112 | b.y = ty;
113 | }
114 | if (p.y < a.y || p.y > b.y) {
115 | return RAY_OUT;
116 | }
117 | if (a.x < b.x) {
118 | if (p.x < a.x) {
119 | return RAY_LEFT;
120 | }
121 | if (p.x > b.x) {
122 | return RAY_OUT;
123 | }
124 | } else {
125 | if (p.x < b.x) {
126 | return RAY_LEFT;
127 | }
128 | if (p.x > a.x) {
129 | return RAY_OUT;
130 | }
131 | }
132 | if ((p.x == a.x && p.y == a.y) || (p.x == b.x && p.y == b.y)) {
133 | // P is on a vertex.
134 | return RAY_ON;
135 | }
136 | double v1 = (p.y - a.y) / (p.x - a.x);
137 | double v2 = (b.y - a.y) / (b.x - a.x);
138 | if (p.x != a.x) {
139 | if (v1-v2 == 0) {
140 | // P is on a segment
141 | return RAY_ON;
142 | }
143 | if (v1 >= v2) {
144 | return RAY_LEFT;
145 | }
146 | return RAY_OUT;
147 | } else {
148 | if (v2 < 0 || (v2 > 0 && p.y < a.y)) {
149 | return RAY_OUT;
150 | } else {
151 | return RAY_LEFT;
152 | }
153 | }
154 | */
155 |
156 | // Algorithm 2 by W. Randolph Franklin
157 | if (a.x == b.x && a.y == b.y) {
158 | if (p.x == a.x && p.y == a.y) {
159 | return RAY_ON;
160 | }
161 | return RAY_OUT;
162 | }
163 | // try to check if p is on line ab and p is on segment ab.
164 | if (((a.x <= p.x && p.x <= b.x) || (b.x <= p.x && p.x <= a.x)) && ((a.y <= p.y && p.y <= b.y) || (b.y <= p.y && p.y <= a.y)) && (p.y - a.y) * (b.x - a.x) == (p.x - a.x) * (b.y - a.y)) {
165 | return RAY_ON;
166 | } else {
167 | // below tries to check:
168 | // min(a.y, b.y) <= p.y < max(a.y, b.y); when ab is parallel with y=p.y, it is always false;
169 | // assume Q is the crosspoint of line y=p.y and line ab, check if p.x < Q.x;
170 | if (((a.y > p.y) != (b.y > p.y)) && (p.x < (b.x - a.x) * (p.y - a.y) / (b.y - a.y) + a.x)) {
171 | return RAY_LEFT;
172 | } else {
173 | return RAY_OUT;
174 | }
175 | }
176 | }
177 |
--------------------------------------------------------------------------------
/src/spatial/rtree.c:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2016, Josh Baker .
3 | * All rights reserved.
4 | *
5 | * Redistribution and use in source and binary forms, with or without
6 | * modification, are permitted provided that the following conditions are met:
7 | *
8 | * * Redistributions of source code must retain the above copyright notice,
9 | * this list of conditions and the following disclaimer.
10 | * * Redistributions in binary form must reproduce the above copyright
11 | * notice, this list of conditions and the following disclaimer in the
12 | * documentation and/or other materials provided with the distribution.
13 | * * Neither the name of Redis nor the names of its contributors may be used
14 | * to endorse or promote products derived from this software without
15 | * specific prior written permission.
16 | *
17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
21 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
27 | * THE POSSIBILITY OF SUCH DAMAGE.
28 | */
29 |
30 | #define NUM_DIMS 2
31 |
32 | #include "zmalloc.h"
33 | #include "rtree_tmpl.c"
34 | #include "rtree.h"
35 |
36 | typedef struct rtreeIterator {
37 | iteratorT *iterator;
38 | } rtreeIterator;
39 |
40 | rtree *rtreeNew() {
41 | rtree *tr = zmalloc(sizeof(rtree));
42 | if (!tr){
43 | return NULL;
44 | }
45 | memset(tr, 0, sizeof(rtree));
46 | return tr;
47 | }
48 |
49 | void rtreeFree(rtree *tr){
50 | if (!tr){
51 | return;
52 | }
53 | if (tr->root){
54 | freeNode(tr->root);
55 | }
56 | zfree(tr);
57 | }
58 |
59 | /* Remove removes item from rtree */
60 | int rtreeRemove(rtree *tr, double minX, double minY, double maxX, double maxY, void *item) {
61 | if (tr && tr->root) {
62 | return removeRect(makeRect(minX, minY, maxX, maxY), item, &(tr->root)) ? 0 : 1;
63 | }
64 | return 0;
65 | }
66 |
67 | // Count return the number of items in rtree.
68 | int rtreeCount(rtree *tr) {
69 | if (!tr || !tr->root){
70 | return 0;
71 | }
72 | return countRec(tr->root, 0);
73 | }
74 |
75 | // Insert inserts item into rtree
76 | int rtreeInsert(rtree *tr, double minX, double minY, double maxX, double maxY, void *item) {
77 | if (!tr){
78 | return 0;
79 | }
80 | if (!tr->root) {
81 | tr->root = zmalloc(sizeof(nodeT));
82 | if (!tr->root){
83 | return 0;
84 | }
85 | memset(tr->root, 0, sizeof(nodeT));
86 | }
87 | insertRect(makeRect(minX, minY, maxX, maxY), item, NULL, &(tr->root), 0);
88 | return 1;
89 | }
90 |
91 | void rtreeRemoveAll(rtree *tr){
92 | if (tr && tr->root){
93 | freeNode(tr->root);
94 | tr->root = NULL;
95 | }
96 | }
97 |
98 | typedef struct iteratorUserData {
99 | rtreeSearchFunc iterator;
100 | void *userdata;
101 | } iteratorUserData;
102 |
103 | static int iteratorFunc(rectT rect, void *item, void *userdata){
104 | iteratorUserData *ud = userdata;
105 | double minX, minY, maxX, maxY;
106 | getRect(rect, &minX, &minY, &maxX, &maxY);
107 | return ud->iterator(minX, minY, maxX, maxY, item, ud->userdata);
108 | }
109 |
110 | int rtreeSearch(rtree *tr, double minX, double minY, double maxX, double maxY, rtreeSearchFunc iterator, void *userdata){
111 | if (!tr || !tr->root){
112 | return 0;
113 | }
114 | if (iterator) {
115 | iteratorUserData ud = {iterator, userdata};
116 | return search(tr->root, makeRect(minX, minY, maxX, maxY), iteratorFunc, &ud);
117 | } else{
118 | return search(tr->root, makeRect(minX, minY, maxX, maxY), NULL, NULL);
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/src/spatial/rtree.h:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2016, Josh Baker .
3 | * All rights reserved.
4 | *
5 | * Redistribution and use in source and binary forms, with or without
6 | * modification, are permitted provided that the following conditions are met:
7 | *
8 | * * Redistributions of source code must retain the above copyright notice,
9 | * this list of conditions and the following disclaimer.
10 | * * Redistributions in binary form must reproduce the above copyright
11 | * notice, this list of conditions and the following disclaimer in the
12 | * documentation and/or other materials provided with the distribution.
13 | * * Neither the name of Redis nor the names of its contributors may be used
14 | * to endorse or promote products derived from this software without
15 | * specific prior written permission.
16 | *
17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
21 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
27 | * THE POSSIBILITY OF SUCH DAMAGE.
28 | */
29 |
30 | #ifndef RTREE_H_
31 | #define RTREE_H_
32 |
33 | #if defined(__cplusplus)
34 | extern "C" {
35 | #endif
36 |
37 | #include "geom.h"
38 |
39 | typedef struct rtree {
40 | void *root;
41 | } rtree;
42 |
43 | rtree *rtreeNew();
44 | void rtreeFree(rtree *tr);
45 | int rtreeRemove(rtree *tr, double minX, double minY, double maxX, double maxY, void *item);
46 | void rtreeRemoveAll(rtree *tr);
47 | int rtreeCount(rtree *tr);
48 | int rtreeInsert(rtree *tr, double minX, double minY, double maxX, double maxY, void *item);
49 | typedef int(*rtreeSearchFunc)(double minX, double minY, double maxX, double maxY, void *item, void *userdata);
50 | int rtreeSearch(rtree *tr, double minX, double minY, double maxX, double maxY, rtreeSearchFunc iterator, void *userdata);
51 |
52 | #if defined(__cplusplus)
53 | }
54 | #endif
55 | #endif /* RTREE_H_ */
56 |
--------------------------------------------------------------------------------
/src/spatial/rtree_test.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include
7 |
8 | #include "test.h"
9 | #include "rtree.h"
10 |
11 | static double randd() { return ((rand()%RAND_MAX) / (double)RAND_MAX); }
12 | static double randx() { return randd() * 360.0 - 180.0; }
13 | static double randy() { return randd() * 180.0 - 90.0; }
14 |
15 | int iterator(double minX, double minY, double maxX, double maxY, void *item, void *userdata){
16 | return 1;
17 | }
18 |
19 | static rtree *insert(){
20 | srand(time(NULL)/clock());
21 | rtree *tr = rtreeNew();
22 | assert(tr);
23 |
24 | int n = 100000;
25 | //clock_t start = clock();
26 | for (int i=0;i= 40 && rtC/n <= 80);
59 | //printf("searched %d queries in %.2f secs, %.0f ops/s (%.0f items)\n", n, elapsed, (double)n/elapsed, rtC/(double)n);
60 | rtreeFree(tr);
61 | return 1;
62 | }
63 |
64 |
65 | int test_RTreeRemove(){
66 | rtree *tr = rtreeNew();
67 | assert(tr);
68 |
69 | assert(rtreeInsert(tr, 10, 10, 20, 20, (void*)(long)100));
70 | assert(rtreeInsert(tr, 30, 30, 50, 50, (void*)(long)101));
71 | assert(rtreeCount(tr)==2);
72 |
73 | assert(rtreeRemove(tr, 30, 30, 50, 50, (void*)(long)101));
74 | assert(rtreeCount(tr)==1);
75 | assert(!rtreeRemove(tr, 30, 30, 50, 50, (void*)(long)101));
76 |
77 | assert(rtreeRemove(tr, 0, 0, 30, 30, (void*)(long)100));
78 | assert(rtreeCount(tr)==0);
79 | assert(!rtreeRemove(tr, 0, 0, 30, 30, (void*)(long)100));
80 |
81 | rtreeRemoveAll(tr);
82 | assert(rtreeCount(tr)==0);
83 |
84 | assert(rtreeInsert(tr, 10, 10, 20, 20, (void*)(long)100));
85 | assert(rtreeInsert(tr, 30, 30, 50, 50, (void*)(long)101));
86 | assert(rtreeCount(tr)==2);
87 |
88 | rtreeRemoveAll(tr);
89 | assert(rtreeCount(tr)==0);
90 | assert(!rtreeRemove(tr, 10, 10, 20, 20, (void*)(long)100));
91 | assert(!rtreeRemove(tr, 30, 30, 50, 50, (void*)(long)101));
92 |
93 |
94 |
95 | rtreeFree(tr);
96 | return 1;
97 | }
--------------------------------------------------------------------------------
/src/spatial/test.c:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2016, Josh Baker .
3 | * All rights reserved.
4 | *
5 | * Redistribution and use in source and binary forms, with or without
6 | * modification, are permitted provided that the following conditions are met:
7 | *
8 | * * Redistributions of source code must retain the above copyright notice,
9 | * this list of conditions and the following disclaimer.
10 | * * Redistributions in binary form must reproduce the above copyright
11 | * notice, this list of conditions and the following disclaimer in the
12 | * documentation and/or other materials provided with the distribution.
13 | * * Neither the name of Redis nor the names of its contributors may be used
14 | * to endorse or promote products derived from this software without
15 | * specific prior written permission.
16 | *
17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
21 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
27 | * THE POSSIBILITY OF SUCH DAMAGE.
28 | */
29 |
30 | #include
31 | #include
32 | #include
33 | #include
34 | #include
35 | #include "test.h"
36 |
37 | int test_Geom();
38 | int test_GeomZ();
39 | int test_GeomZM();
40 | int test_GeomPoint();
41 | int test_GeomMultiPoint();
42 | int test_GeomLineString();
43 | int test_GeomMultiLineString();
44 | int test_GeomPolygon();
45 | int test_GeomMultiPolygon();
46 | int test_GeomGeometryCollection();
47 | int test_GeomIterator();
48 | int test_GeomPolyMap();
49 | int test_RTreeInsert();
50 | int test_RTreeSearch();
51 | int test_RTreeRemove();
52 | int test_GeoUtilDistance();
53 | int test_GeoUtilDestination();
54 | int test_PolyRayInside();
55 | int test_PolyRayExteriorHoles();
56 | int test_PolyInsideShapes();
57 | int test_PolyIntersectsLines();
58 | int test_PolyIntersectsShapes();
59 | int test_PolyRectIntersects();
60 | int test_PolyRectInside();
61 |
62 |
63 | int test_GeomPolyMapPointBench();
64 | int test_GeomPolyMapPolygonBench();
65 | int test_GeomPolyMapGeometryCollectionBench();
66 |
67 | int test_GeomPolyMapPointBenchSingleThreaded();
68 | int test_GeomPolyMapPolygonBenchSingleThreaded();
69 | int test_GeomPolyMapGeometryCollectionBenchSingleThreaded();
70 |
71 | int test_GeomPolyMapIntersects();
72 | int test_GeomPolyMapWithin();
73 |
74 |
75 |
76 |
77 | typedef struct test{
78 | char *name;
79 | int (*test)();
80 | } test;
81 |
82 | test tests[] = {
83 | { "geom", test_Geom },
84 | { "geomZ", test_GeomZ },
85 | { "geomZM", test_GeomZM },
86 | { "geomPoint", test_GeomPoint },
87 | { "geomMultiPoint", test_GeomMultiPoint },
88 | { "geomLineString", test_GeomLineString },
89 | { "geomMultiLineString", test_GeomMultiLineString },
90 | { "geomPolygon", test_GeomPolygon },
91 | { "geomMultiPolygon", test_GeomMultiPolygon },
92 | { "geomGeometryCollection", test_GeomGeometryCollection },
93 | { "geomIterator", test_GeomIterator },
94 | { "geomPolyMap", test_GeomPolyMap },
95 |
96 | { "rtreeInsert", test_RTreeInsert },
97 | { "rtreeSearch", test_RTreeSearch },
98 | { "rtreeRemove", test_RTreeRemove },
99 |
100 | { "geoutilDistance", test_GeoUtilDistance },
101 | { "geoutilDestination", test_GeoUtilDestination },
102 |
103 | { "polyRayInside", test_PolyRayInside },
104 | { "polyRayExteriorHoles", test_PolyRayExteriorHoles },
105 | { "polyInsideShapes", test_PolyInsideShapes },
106 | { "polyIntersectsLines", test_PolyIntersectsLines },
107 | { "polyIntersectsShapes", test_PolyIntersectsShapes },
108 | { "polyRectIntersects", test_PolyRectIntersects },
109 | { "polyRectInside", test_PolyRectInside },
110 |
111 | { "polyMapPointBench", test_GeomPolyMapPointBench },
112 | { "polyMapPolygonBench", test_GeomPolyMapPolygonBench },
113 | { "polyMapGeomColBench", test_GeomPolyMapGeometryCollectionBench },
114 | { "polyMapPointBenchSingleThreaded", test_GeomPolyMapPointBenchSingleThreaded },
115 | { "polyMapPolygonBenchSingleThreaded", test_GeomPolyMapPolygonBenchSingleThreaded },
116 | { "polyMapGeomColBenchSingleThreaded", test_GeomPolyMapGeometryCollectionBenchSingleThreaded },
117 |
118 | { "searchPolyMapIntersects", test_GeomPolyMapIntersects },
119 | { "searchPolyMapWithin", test_GeomPolyMapWithin },
120 |
121 | };
122 |
123 | static int abort_handled = 0;
124 | void __tassert_fail(const char *what, const char *file, int line, const char *func){
125 | printf("\x1b[31m[failed]\x1b[0m\n");
126 | printf(" fail: assert(%s)\n func: %s\n file: %s\n line: %d\n", what, func, file, line);
127 | abort_handled = 1;
128 | abort();
129 | }
130 |
131 | void sig_handler(int sig) {
132 | if (!abort_handled){
133 | printf("\x1b[31m[failed]\x1b[0m\n");
134 | if (sig == SIGABRT){
135 | printf(" \x1b[33muse \"test.h\" for more details.\x1b[0m\n");
136 | }
137 | abort_handled = 1;
138 | }else {
139 | printf("\x1b[0m\n");
140 | }
141 | if (sig == SIGSEGV){
142 | printf("Segmentation fault: 11\n");
143 | }
144 | exit(1);
145 | }
146 |
147 | static clock_t start_c = 0;
148 | static clock_t stop_c = 0;
149 |
150 | void stopClock(){
151 | stop_c = clock();
152 | }
153 |
154 | void restartClock(){
155 | start_c = clock();
156 | stop_c = 0;
157 | }
158 |
159 | int main(int argc, const char **argv) {
160 | signal(SIGSEGV, sig_handler);
161 | signal(SIGTERM, sig_handler);
162 | signal(SIGHUP, sig_handler);
163 | signal(SIGINT, sig_handler);
164 | signal(SIGABRT, sig_handler);
165 |
166 | const char *run = getenv("RUNTEST");
167 | if (run == NULL){
168 | run = "";
169 | }
170 |
171 | for (int i=1;i mlabelsz){
196 | mlabelsz = strlen(label);
197 | }
198 | }
199 |
200 | for (int i=0;i 1){
223 | double opss = ((double)res)/elapsed;
224 | printf(", op/s %.0f", opss);
225 | }
226 | printf("\n");
227 | }
228 | }
229 | }
230 |
--------------------------------------------------------------------------------
/src/spatial/test.h:
--------------------------------------------------------------------------------
1 | #ifndef TEST_H_
2 | #define TEST_H_
3 |
4 | #if defined(__cplusplus)
5 | extern "C" {
6 | #endif
7 |
8 | #include
9 |
10 | #define tassert(x) ((void)((x) || (__tassert_fail(#x, __FILE__, __LINE__, __func__),0)))
11 | #ifdef assert
12 | #undef assert
13 | #define assert tassert
14 | #endif
15 |
16 | void __tassert_fail(const char *what, const char *file, int line, const char *func);
17 |
18 | void stopClock();
19 | void restartClock();
20 |
21 | #if defined(__cplusplus)
22 | }
23 | #endif
24 | #endif /* TEST_H_ */
--------------------------------------------------------------------------------
/src/spatial/zmalloc.h:
--------------------------------------------------------------------------------
1 | /* For now just use stdlib malloc, but may be expaned in the future. */
2 |
3 | #ifndef ZMALLOC_H_
4 | #define ZMALLOC_H_
5 |
6 | #if defined(__cplusplus)
7 | extern "C" {
8 | #endif
9 |
10 | #include "../redismodule.h"
11 |
12 | #define zmalloc(size) (RedisModule_Alloc((size)))
13 | #define zrealloc(ptr,size) (RedisModule_Realloc((ptr),(size)))
14 | #define zfree(ptr) (RedisModule_Free((ptr)))
15 |
16 | #if defined(__cplusplus)
17 | }
18 | #endif
19 | #endif /* ZMALLOC_H_ */
--------------------------------------------------------------------------------
/src/tairgis.h:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023 Alibaba Tair Team
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | #ifndef TAIRGIS_H
18 | #define TAIRGIS_H
19 |
20 | #define GIS_WITHVALUE (1<<0)
21 | #define GIS_WITHDIST (1<<1)
22 | #define GIS_SORT_ASC (1<<2)
23 | #define GIS_SORT_DESC (1<<3)
24 |
25 | #endif // TAIRGIS_H
26 |
--------------------------------------------------------------------------------
/src/util.c:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023 Alibaba Tair Team
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | #include "util.h"
18 | #include "redismodule.h"
19 |
20 | #include
21 |
22 | /* Return the number of digits of 'v' when converted to string in radix 10.
23 | * See ll2string() for more information. */
24 | uint32_t GisModule_digits10(uint64_t v) {
25 | if (v < 10) return 1;
26 | if (v < 100) return 2;
27 | if (v < 1000) return 3;
28 | if (v < 1000000000000UL) {
29 | if (v < 100000000UL) {
30 | if (v < 1000000) {
31 | if (v < 10000) return 4;
32 | return (uint32_t) 5 + (v >= 100000);
33 | }
34 | return (uint32_t) 7 + (v >= 10000000UL);
35 | }
36 | if (v < 10000000000UL) {
37 | return (uint32_t) 9 + (v >= 1000000000UL);
38 | }
39 | return (uint32_t) 11 + (v >= 100000000000UL);
40 | }
41 | return 12 + GisModule_digits10(v / 1000000000000UL);
42 | }
43 |
44 | /* Convert a long long into a string. Returns the number of
45 | * characters needed to represent the number.
46 | * If the buffer is not big enough to store the string, 0 is returned.
47 | *
48 | * Based on the following article (that apparently does not provide a
49 | * novel approach but only publicizes an already used technique):
50 | *
51 | * https://www.facebook.com/notes/facebook-engineering/three-optimization-tips-for-c/10151361643253920
52 | *
53 | * Modified in order to handle signed integers since the original code was
54 | * designed for unsigned integers. */
55 | int GisModule_ll2string(char *dst, size_t dstlen, long long svalue) {
56 | static const char digits[201] =
57 | "0001020304050607080910111213141516171819"
58 | "2021222324252627282930313233343536373839"
59 | "4041424344454647484950515253545556575859"
60 | "6061626364656667686970717273747576777879"
61 | "8081828384858687888990919293949596979899";
62 | int negative;
63 | unsigned long long value;
64 |
65 | /* The main loop works with 64bit unsigned integers for simplicity, so
66 | * we convert the number here and remember if it is negative. */
67 | if (svalue < 0) {
68 | if (svalue != LLONG_MIN) {
69 | value = -svalue;
70 | } else {
71 | value = ((unsigned long long) LLONG_MAX)+1;
72 | }
73 | negative = 1;
74 | } else {
75 | value = svalue;
76 | negative = 0;
77 | }
78 |
79 | /* Check length. */
80 | uint32_t const length = GisModule_digits10(value)+negative;
81 | if (length >= dstlen) return 0;
82 |
83 | /* Null term. */
84 | uint32_t next = length;
85 | dst[next] = '\0';
86 | next--;
87 | while (value >= 100) {
88 | int const i = (value % 100) * 2;
89 | value /= 100;
90 | dst[next] = digits[i + 1];
91 | dst[next - 1] = digits[i];
92 | next -= 2;
93 | }
94 |
95 | /* Handle last 1-2 digits. */
96 | if (value < 10) {
97 | dst[next] = '0' + (uint32_t) value;
98 | } else {
99 | int i = (uint32_t) value * 2;
100 | dst[next] = digits[i + 1];
101 | dst[next - 1] = digits[i];
102 | }
103 |
104 | /* Add sign. */
105 | if (negative) dst[0] = '-';
106 | return length;
107 | }
108 |
109 | int GisModule_DictInsertOrUpdate(RedisModuleDict *d, RedisModuleString *field, RedisModuleString *value) {
110 | int nokey = 0;
111 | RedisModuleString *oldvalue, *newvalue = RedisModule_CreateStringFromString(NULL, value);
112 | nokey = RedisModule_DictDel(d, field, &oldvalue);
113 | if (nokey == REDISMODULE_OK) {
114 | GisModule_FreeStringSafe(NULL, oldvalue);
115 | }
116 | return RedisModule_DictSet(d, field, newvalue);
117 | }
118 |
119 | void GisModule_FreeStringSafe(RedisModuleCtx *ctx, RedisModuleString *str) {
120 | if (!str) return;
121 | RedisModule_FreeString(ctx, str);
122 | }
123 |
124 | int GisModule_GetDoubleFromObjectOrReply(RedisModuleCtx *ctx, RedisModuleString *s, double *target, const char *msg) {
125 | double value;
126 | if (RedisModule_StringToDouble(s, &value) != REDISMODULE_OK) {
127 | if (msg != NULL) {
128 | RedisModule_ReplyWithError(ctx, (char *) msg);
129 | } else {
130 | RedisModule_ReplyWithError(ctx, "ERR value is not a valid float");
131 | }
132 | return REDISMODULE_ERR;
133 | }
134 | *target = value;
135 | return REDISMODULE_OK;
136 | }
137 |
138 | int GisModule_ExtractUnitOrReply(RedisModuleCtx *ctx, RedisModuleString *sunit, double *to_meters) {
139 | const char *unit = RedisModule_StringPtrLen(sunit, NULL);
140 | if (!strcasecmp(unit, "m")) {
141 | *to_meters = 1;
142 | } else if (!strcasecmp(unit, "km")) {
143 | *to_meters = 1000;
144 | } else if (!strcasecmp(unit, "ft")) {
145 | *to_meters = 0.3048;
146 | } else if (!strcasecmp(unit, "mi")) {
147 | *to_meters = 1609.34;
148 | } else {
149 | RedisModule_ReplyWithError(ctx,
150 | "ERR unsupported unit provided. please use m, km, ft, mi");
151 | return REDISMODULE_ERR;
152 | }
153 | return REDISMODULE_OK;
154 | }
155 |
156 | void GisModule_AddReplyDistance(RedisModuleCtx *ctx, double d) {
157 | char dbuf[128];
158 | size_t dlen = snprintf(dbuf, sizeof(dbuf), "%.4f", d);
159 | RedisModule_ReplyWithStringBuffer(ctx, dbuf, dlen);
160 | }
161 |
162 |
--------------------------------------------------------------------------------
/src/util.h:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023 Alibaba Tair Team
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | #ifndef UTIL_H
18 | #define UTIL_H
19 |
20 | #include
21 | #include
22 | #include
23 | #include "redismodule.h"
24 |
25 | uint32_t GisModule_digits10(uint64_t v);
26 | int GisModule_ll2string(char *dst, size_t dstlen, long long svalue);
27 | int GisModule_DictInsertOrUpdate(RedisModuleDict *d, RedisModuleString *field, RedisModuleString *value);
28 | void GisModule_FreeStringSafe(RedisModuleCtx *ctx, RedisModuleString *str);
29 | int GisModule_GetDoubleFromObjectOrReply(RedisModuleCtx *ctx, RedisModuleString *s, double *target, const char *msg);
30 | int GisModule_ExtractUnitOrReply(RedisModuleCtx *ctx, RedisModuleString *unit, double *to_meters);
31 | void GisModule_AddReplyDistance(RedisModuleCtx *ctx, double d);
32 |
33 | #endif // UTIL_H
34 |
--------------------------------------------------------------------------------