1
2
3
4
5
6
7
8
9 package org.locationtech.spatial4j.context;
10
11 import org.locationtech.spatial4j.distance.CartesianDistCalc;
12 import org.locationtech.spatial4j.distance.DistanceCalculator;
13 import org.locationtech.spatial4j.distance.GeodesicSphereDistCalc;
14 import org.locationtech.spatial4j.io.*;
15 import org.locationtech.spatial4j.shape.Rectangle;
16 import org.locationtech.spatial4j.shape.ShapeFactory;
17 import org.locationtech.spatial4j.shape.impl.ShapeFactoryImpl;
18
19 import java.lang.reflect.Constructor;
20 import java.lang.reflect.Field;
21 import java.util.*;
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55 public class SpatialContextFactory {
56
57
58 protected Map<String, String> args;
59
60 protected ClassLoader classLoader;
61
62
63
64 public boolean geo = true;
65 public DistanceCalculator distCalc;
66 public Rectangle worldBounds;
67
68 public boolean normWrapLongitude = false;
69
70 public Class<? extends ShapeFactory> shapeFactoryClass = ShapeFactoryImpl.class;
71 public Class<? extends BinaryCodec> binaryCodecClass = BinaryCodec.class;
72 public final List<Class<? extends ShapeReader>> readers = new ArrayList<>();
73 public final List<Class<? extends ShapeWriter>> writers = new ArrayList<>();
74 public boolean hasFormatConfig = false;
75
76 public SpatialContextFactory() {
77 }
78
79
80
81
82
83
84
85
86
87
88
89
90
91 public static SpatialContext makeSpatialContext(Map<String,String> args, ClassLoader classLoader) {
92 if (classLoader == null)
93 classLoader = SpatialContextFactory.class.getClassLoader();
94 SpatialContextFactory instance;
95 String cname = args.get("spatialContextFactory");
96 if (cname == null)
97 cname = System.getProperty("SpatialContextFactory");
98 if (cname == null)
99 instance = new SpatialContextFactory();
100 else {
101 try {
102 Class<?> c = classLoader.loadClass(cname);
103 instance = (SpatialContextFactory) c.newInstance();
104 } catch (Exception e) {
105 throw new RuntimeException(e);
106 }
107 }
108 instance.init(args, classLoader);
109 return instance.newSpatialContext();
110 }
111
112 protected void init(Map<String, String> args, ClassLoader classLoader) {
113 this.args = args;
114 this.classLoader = classLoader;
115
116 initField("geo");
117
118 initField("shapeFactoryClass");
119
120 initCalculator();
121
122
123 initFormats();
124 initWorldBounds();
125
126 initField("normWrapLongitude");
127
128 initField("binaryCodecClass");
129 }
130
131
132 @SuppressWarnings("unchecked")
133 protected void initField(String name) {
134
135 Field field;
136 try {
137 field = getClass().getField(name);
138 } catch (NoSuchFieldException e) {
139 throw new Error(e);
140 }
141 String str = args.get(name);
142 if (str != null) {
143 try {
144 Object o;
145 if (field.getType() == Boolean.TYPE) {
146 o = Boolean.valueOf(str);
147 } else if (field.getType() == Class.class) {
148 try {
149 o = classLoader.loadClass(str);
150 } catch (ClassNotFoundException e) {
151 throw new RuntimeException(e);
152 }
153 } else if (field.getType().isEnum()) {
154 o = Enum.valueOf(field.getType().asSubclass(Enum.class), str);
155 } else {
156 throw new Error("unsupported field type: "+field.getType());
157 }
158 field.set(this, o);
159 } catch (IllegalAccessException e) {
160 throw new Error(e);
161 } catch (Exception e) {
162 throw new RuntimeException(
163 "Invalid value '"+str+"' on field "+name+" of type "+field.getType(), e);
164 }
165 }
166 }
167
168 protected void initCalculator() {
169 String calcStr = args.get("distCalculator");
170 if (calcStr == null)
171 return;
172 if (calcStr.equalsIgnoreCase("haversine")) {
173 distCalc = new GeodesicSphereDistCalc.Haversine();
174 } else if (calcStr.equalsIgnoreCase("lawOfCosines")) {
175 distCalc = new GeodesicSphereDistCalc.LawOfCosines();
176 } else if (calcStr.equalsIgnoreCase("vincentySphere")) {
177 distCalc = new GeodesicSphereDistCalc.Vincenty();
178 } else if (calcStr.equalsIgnoreCase("cartesian")) {
179 distCalc = new CartesianDistCalc();
180 } else if (calcStr.equalsIgnoreCase("cartesian^2")) {
181 distCalc = new CartesianDistCalc(true);
182 } else {
183 throw new RuntimeException("Unknown calculator: "+calcStr);
184 }
185 }
186
187
188
189
190
191
192
193 protected void initFormats() {
194 try {
195 String val = args.get("readers");
196 if (val != null) {
197 for (String name : val.split(",")) {
198 readers.add(Class.forName(name.trim(), false, classLoader).asSubclass(ShapeReader.class));
199 }
200 } else {
201 val = args.get("wktShapeParserClass");
202 if (val != null) {
203
204 readers.add(Class.forName(val.trim(), false, classLoader).asSubclass(ShapeReader.class));
205 }
206 }
207 val = args.get("writers");
208 if (val != null) {
209 for (String name : val.split(",")) {
210 writers.add(Class.forName(name.trim(), false, classLoader).asSubclass(ShapeWriter.class));
211 }
212 }
213 } catch (ClassNotFoundException ex) {
214 throw new RuntimeException("Unable to find format class", ex);
215 }
216 }
217
218
219 public SupportedFormats makeFormats(SpatialContext ctx) {
220 checkDefaultFormats();
221
222 List<ShapeReader> read = new ArrayList<>(readers.size());
223 for (Class<? extends ShapeReader> clazz : readers) {
224 try {
225 read.add(makeClassInstance(clazz, ctx, this));
226 } catch (Exception ex) {
227 throw new RuntimeException(ex);
228 }
229 }
230
231 List<ShapeWriter> write = new ArrayList<>(writers.size());
232 for (Class<? extends ShapeWriter> clazz : writers) {
233 try {
234 write.add(makeClassInstance(clazz, ctx, this));
235 } catch (Exception ex) {
236 throw new RuntimeException(ex);
237 }
238 }
239
240 return new SupportedFormats(
241 Collections.unmodifiableList(read),
242 Collections.unmodifiableList(write));
243 }
244
245
246
247
248 protected void checkDefaultFormats() {
249 if (readers.isEmpty()) {
250 addReaderIfNoggitExists(GeoJSONReader.class);
251 readers.add(WKTReader.class);
252 readers.add(PolyshapeReader.class);
253 readers.add(LegacyShapeReader.class);
254 }
255 if (writers.isEmpty()) {
256 writers.add(GeoJSONWriter.class);
257 writers.add(WKTWriter.class);
258 writers.add(PolyshapeWriter.class);
259 writers.add(LegacyShapeWriter.class);
260 }
261 }
262
263 public void addReaderIfNoggitExists(Class<? extends ShapeReader> reader) {
264 try {
265 if (classLoader==null) {
266 Class.forName("org.noggit.JSONParser");
267 } else {
268 Class.forName("org.noggit.JSONParser", true, classLoader);
269 }
270 readers.add(reader);
271 } catch (ClassNotFoundException e) {
272
273 }
274 }
275
276 protected void initWorldBounds() {
277 String worldBoundsStr = args.get("worldBounds");
278 if (worldBoundsStr == null)
279 return;
280
281
282 final SpatialContext ctx = newSpatialContext();
283 worldBounds = (Rectangle) ctx.readShape(worldBoundsStr);
284 }
285
286
287 public SpatialContext newSpatialContext() {
288 return new SpatialContext(this);
289 }
290
291 public ShapeFactory makeShapeFactory(SpatialContext ctx) {
292 return makeClassInstance(shapeFactoryClass, ctx, this);
293 }
294
295 public BinaryCodec makeBinaryCodec(SpatialContext ctx) {
296 return makeClassInstance(binaryCodecClass, ctx, this);
297 }
298
299 private <T> T makeClassInstance(Class<? extends T> clazz, Object... ctorArgs) {
300 try {
301 Constructor<?> empty = null;
302
303
304 ctorLoop: for (Constructor<?> ctor : clazz.getConstructors()) {
305 Class<?>[] parameterTypes = ctor.getParameterTypes();
306 if (parameterTypes.length == 0) {
307 empty = ctor;
308 }
309 if (parameterTypes.length != ctorArgs.length)
310 continue;
311 for (int i = 0; i < ctorArgs.length; i++) {
312 Object ctorArg = ctorArgs[i];
313 if (!parameterTypes[i].isAssignableFrom(ctorArg.getClass()))
314 continue ctorLoop;
315 }
316 return clazz.cast(ctor.newInstance(ctorArgs));
317 }
318
319
320 if (empty != null) {
321 return clazz.cast(empty.newInstance());
322 }
323 } catch (Exception e) {
324 throw new RuntimeException(e);
325 }
326 throw new RuntimeException(clazz + " needs a constructor that takes: "
327 + Arrays.toString(ctorArgs));
328 }
329
330 }