guohanghui commited on
Commit
8f11efc
·
verified ·
1 Parent(s): b8dfb4e

Update pygeos/mcp_output/mcp_plugin/mcp_service.py

Browse files
pygeos/mcp_output/mcp_plugin/mcp_service.py CHANGED
@@ -1,44 +1,746 @@
 
 
 
 
1
  from fastmcp import FastMCP
 
 
2
 
3
  # Create the FastMCP service application
4
  mcp = FastMCP("pygeos_service")
5
 
6
- @mcp.tool(name="list_geometries", description="List all available geometry types in PyGEOS")
7
- def list_geometries() -> dict:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  """
9
- List all available geometry types in PyGEOS.
 
 
 
 
 
 
 
10
 
 
 
 
 
 
 
 
 
11
  Returns:
12
- - dict: A dictionary with success status and list of geometry types.
13
  """
14
  try:
15
- from pygeos import GeometryType
16
- geometries = [g.name for g in GeometryType]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  return {
18
  "success": True,
19
- "geometries": geometries,
20
- "count": len(geometries)
 
 
 
21
  }
22
  except Exception as e:
23
  return {"success": False, "error": str(e)}
24
 
25
- @mcp.tool(name="create_geometry", description="Create a geometry object in PyGEOS")
26
- def create_geometry(wkt: str) -> dict:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
  """
28
- Create a geometry object from a WKT string.
 
 
 
 
 
 
 
29
 
30
- Parameters:
31
- - wkt: Well-Known Text representation of the geometry
32
 
 
 
 
 
 
 
 
 
33
  Returns:
34
- - dict: Information about the created geometry
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
  """
36
  try:
37
- from pygeos import from_wkt
38
- geometry = from_wkt(wkt)
39
  return {
40
  "success": True,
41
- "geometry": str(geometry)
 
42
  }
43
  except Exception as e:
44
- return {"success": False, "error": str(e)}
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import sys
3
+ from typing import List, Optional
4
+
5
  from fastmcp import FastMCP
6
+ import numpy as np
7
+ import pygeos
8
 
9
  # Create the FastMCP service application
10
  mcp = FastMCP("pygeos_service")
11
 
12
+ # ==================== Geometry Creation ====================
13
+
14
+ @mcp.tool()
15
+ def create_point(x: float, y: float, z: Optional[float] = None) -> dict:
16
+ """Create a point geometry from coordinates.
17
+
18
+ Args:
19
+ x: X coordinate
20
+ y: Y coordinate
21
+ z: Optional Z coordinate for 3D point
22
+
23
+ Returns:
24
+ dict: WKT representation of the created point
25
+ """
26
+ try:
27
+ if z is not None:
28
+ coords = [[x, y, z]]
29
+ else:
30
+ coords = [[x, y]]
31
+ point = pygeos.points(coords)[0]
32
+ wkt = pygeos.to_wkt(point)
33
+ return {"success": True, "wkt": wkt, "geometry_type": "Point"}
34
+ except Exception as e:
35
+ return {"success": False, "error": str(e)}
36
+
37
+
38
+ @mcp.tool()
39
+ def create_linestring(coordinates: List[List[float]]) -> dict:
40
+ """Create a linestring geometry from a list of coordinates.
41
+
42
+ Args:
43
+ coordinates: List of [x, y] or [x, y, z] coordinate pairs, e.g. [[0, 0], [1, 1], [2, 0]]
44
+
45
+ Returns:
46
+ dict: WKT representation of the created linestring
47
  """
48
+ try:
49
+ coords = np.array(coordinates)
50
+ linestring = pygeos.linestrings([coords])[0]
51
+ wkt = pygeos.to_wkt(linestring)
52
+ return {"success": True, "wkt": wkt, "geometry_type": "LineString"}
53
+ except Exception as e:
54
+ return {"success": False, "error": str(e)}
55
+
56
 
57
+ @mcp.tool()
58
+ def create_polygon(shell: List[List[float]], holes: Optional[List[List[List[float]]]] = None) -> dict:
59
+ """Create a polygon geometry from shell and optional holes.
60
+
61
+ Args:
62
+ shell: List of coordinates forming the exterior ring, e.g. [[0, 0], [1, 0], [1, 1], [0, 1], [0, 0]]
63
+ holes: Optional list of hole rings
64
+
65
  Returns:
66
+ dict: WKT representation of the created polygon
67
  """
68
  try:
69
+ shell_ring = pygeos.linearrings([np.array(shell)])[0]
70
+ if holes:
71
+ hole_rings = [pygeos.linearrings([np.array(h)])[0] for h in holes]
72
+ polygon = pygeos.polygons(shell_ring, hole_rings)
73
+ else:
74
+ polygon = pygeos.polygons(shell_ring)
75
+ wkt = pygeos.to_wkt(polygon)
76
+ return {"success": True, "wkt": wkt, "geometry_type": "Polygon"}
77
+ except Exception as e:
78
+ return {"success": False, "error": str(e)}
79
+
80
+
81
+ @mcp.tool()
82
+ def create_box(xmin: float, ymin: float, xmax: float, ymax: float) -> dict:
83
+ """Create a rectangular polygon (box) from bounds.
84
+
85
+ Args:
86
+ xmin: Minimum X coordinate
87
+ ymin: Minimum Y coordinate
88
+ xmax: Maximum X coordinate
89
+ ymax: Maximum Y coordinate
90
+
91
+ Returns:
92
+ dict: WKT representation of the box polygon
93
+ """
94
+ try:
95
+ box = pygeos.box(xmin, ymin, xmax, ymax)
96
+ wkt = pygeos.to_wkt(box)
97
+ return {"success": True, "wkt": wkt, "geometry_type": "Polygon"}
98
+ except Exception as e:
99
+ return {"success": False, "error": str(e)}
100
+
101
+
102
+ @mcp.tool()
103
+ def from_wkt(wkt: str) -> dict:
104
+ """Parse a WKT string into a geometry and return its properties.
105
+
106
+ Args:
107
+ wkt: Well-Known Text representation of geometry
108
+
109
+ Returns:
110
+ dict: Geometry properties including type, validity, and bounds
111
+ """
112
+ try:
113
+ geom = pygeos.from_wkt(wkt)
114
+ geom_type = pygeos.get_type_id(geom)
115
+ type_names = {0: "Point", 1: "LineString", 2: "LinearRing", 3: "Polygon",
116
+ 4: "MultiPoint", 5: "MultiLineString", 6: "MultiPolygon", 7: "GeometryCollection"}
117
+ bounds = pygeos.bounds(geom).tolist()
118
  return {
119
  "success": True,
120
+ "wkt": pygeos.to_wkt(geom),
121
+ "geometry_type": type_names.get(int(geom_type), "Unknown"),
122
+ "is_valid": bool(pygeos.is_valid(geom)),
123
+ "is_empty": bool(pygeos.is_empty(geom)),
124
+ "bounds": {"xmin": bounds[0], "ymin": bounds[1], "xmax": bounds[2], "ymax": bounds[3]}
125
  }
126
  except Exception as e:
127
  return {"success": False, "error": str(e)}
128
 
129
+
130
+ # ==================== Measurement ====================
131
+
132
+ @mcp.tool()
133
+ def calculate_distance(wkt1: str, wkt2: str) -> dict:
134
+ """Calculate the Cartesian distance between two geometries.
135
+
136
+ Args:
137
+ wkt1: WKT of the first geometry
138
+ wkt2: WKT of the second geometry
139
+
140
+ Returns:
141
+ dict: Distance between the geometries
142
+ """
143
+ try:
144
+ geom1 = pygeos.from_wkt(wkt1)
145
+ geom2 = pygeos.from_wkt(wkt2)
146
+ distance = float(pygeos.distance(geom1, geom2))
147
+ return {"success": True, "distance": distance}
148
+ except Exception as e:
149
+ return {"success": False, "error": str(e)}
150
+
151
+
152
+ @mcp.tool()
153
+ def calculate_area(wkt: str) -> dict:
154
+ """Calculate the area of a polygon geometry.
155
+
156
+ Args:
157
+ wkt: WKT of the polygon geometry
158
+
159
+ Returns:
160
+ dict: Area of the polygon
161
+ """
162
+ try:
163
+ geom = pygeos.from_wkt(wkt)
164
+ area = float(pygeos.area(geom))
165
+ return {"success": True, "area": area}
166
+ except Exception as e:
167
+ return {"success": False, "error": str(e)}
168
+
169
+
170
+ @mcp.tool()
171
+ def calculate_length(wkt: str) -> dict:
172
+ """Calculate the length of a linear geometry.
173
+
174
+ Args:
175
+ wkt: WKT of the linear geometry (LineString, MultiLineString, or polygon boundary)
176
+
177
+ Returns:
178
+ dict: Length of the geometry
179
+ """
180
+ try:
181
+ geom = pygeos.from_wkt(wkt)
182
+ length = float(pygeos.length(geom))
183
+ return {"success": True, "length": length}
184
+ except Exception as e:
185
+ return {"success": False, "error": str(e)}
186
+
187
+
188
+ @mcp.tool()
189
+ def get_bounds(wkt: str) -> dict:
190
+ """Get the bounding box of a geometry.
191
+
192
+ Args:
193
+ wkt: WKT of the geometry
194
+
195
+ Returns:
196
+ dict: Bounding box coordinates (xmin, ymin, xmax, ymax)
197
+ """
198
+ try:
199
+ geom = pygeos.from_wkt(wkt)
200
+ bounds = pygeos.bounds(geom).tolist()
201
+ return {
202
+ "success": True,
203
+ "xmin": bounds[0],
204
+ "ymin": bounds[1],
205
+ "xmax": bounds[2],
206
+ "ymax": bounds[3]
207
+ }
208
+ except Exception as e:
209
+ return {"success": False, "error": str(e)}
210
+
211
+
212
+ @mcp.tool()
213
+ def hausdorff_distance(wkt1: str, wkt2: str) -> dict:
214
+ """Calculate the Hausdorff distance between two geometries.
215
+
216
+ The Hausdorff distance is a measure of the maximum distance between
217
+ the nearest points in two geometries.
218
+
219
+ Args:
220
+ wkt1: WKT of the first geometry
221
+ wkt2: WKT of the second geometry
222
+
223
+ Returns:
224
+ dict: Hausdorff distance
225
+ """
226
+ try:
227
+ geom1 = pygeos.from_wkt(wkt1)
228
+ geom2 = pygeos.from_wkt(wkt2)
229
+ dist = float(pygeos.hausdorff_distance(geom1, geom2))
230
+ return {"success": True, "hausdorff_distance": dist}
231
+ except Exception as e:
232
+ return {"success": False, "error": str(e)}
233
+
234
+
235
+ # ==================== Predicates ====================
236
+
237
+ @mcp.tool()
238
+ def check_intersects(wkt1: str, wkt2: str) -> dict:
239
+ """Check if two geometries intersect.
240
+
241
+ Args:
242
+ wkt1: WKT of the first geometry
243
+ wkt2: WKT of the second geometry
244
+
245
+ Returns:
246
+ dict: Boolean indicating intersection
247
+ """
248
+ try:
249
+ geom1 = pygeos.from_wkt(wkt1)
250
+ geom2 = pygeos.from_wkt(wkt2)
251
+ result = bool(pygeos.intersects(geom1, geom2))
252
+ return {"success": True, "intersects": result}
253
+ except Exception as e:
254
+ return {"success": False, "error": str(e)}
255
+
256
+
257
+ @mcp.tool()
258
+ def check_contains(wkt1: str, wkt2: str) -> dict:
259
+ """Check if geometry 1 contains geometry 2.
260
+
261
+ Args:
262
+ wkt1: WKT of the container geometry
263
+ wkt2: WKT of the contained geometry
264
+
265
+ Returns:
266
+ dict: Boolean indicating containment
267
+ """
268
+ try:
269
+ geom1 = pygeos.from_wkt(wkt1)
270
+ geom2 = pygeos.from_wkt(wkt2)
271
+ result = bool(pygeos.contains(geom1, geom2))
272
+ return {"success": True, "contains": result}
273
+ except Exception as e:
274
+ return {"success": False, "error": str(e)}
275
+
276
+
277
+ @mcp.tool()
278
+ def check_within(wkt1: str, wkt2: str) -> dict:
279
+ """Check if geometry 1 is within geometry 2.
280
+
281
+ Args:
282
+ wkt1: WKT of the inner geometry
283
+ wkt2: WKT of the outer geometry
284
+
285
+ Returns:
286
+ dict: Boolean indicating if geometry 1 is within geometry 2
287
+ """
288
+ try:
289
+ geom1 = pygeos.from_wkt(wkt1)
290
+ geom2 = pygeos.from_wkt(wkt2)
291
+ result = bool(pygeos.within(geom1, geom2))
292
+ return {"success": True, "within": result}
293
+ except Exception as e:
294
+ return {"success": False, "error": str(e)}
295
+
296
+
297
+ @mcp.tool()
298
+ def check_touches(wkt1: str, wkt2: str) -> dict:
299
+ """Check if two geometries touch (share boundary but not interior).
300
+
301
+ Args:
302
+ wkt1: WKT of the first geometry
303
+ wkt2: WKT of the second geometry
304
+
305
+ Returns:
306
+ dict: Boolean indicating if geometries touch
307
+ """
308
+ try:
309
+ geom1 = pygeos.from_wkt(wkt1)
310
+ geom2 = pygeos.from_wkt(wkt2)
311
+ result = bool(pygeos.touches(geom1, geom2))
312
+ return {"success": True, "touches": result}
313
+ except Exception as e:
314
+ return {"success": False, "error": str(e)}
315
+
316
+
317
+ @mcp.tool()
318
+ def check_crosses(wkt1: str, wkt2: str) -> dict:
319
+ """Check if two geometries cross each other.
320
+
321
+ Args:
322
+ wkt1: WKT of the first geometry
323
+ wkt2: WKT of the second geometry
324
+
325
+ Returns:
326
+ dict: Boolean indicating if geometries cross
327
+ """
328
+ try:
329
+ geom1 = pygeos.from_wkt(wkt1)
330
+ geom2 = pygeos.from_wkt(wkt2)
331
+ result = bool(pygeos.crosses(geom1, geom2))
332
+ return {"success": True, "crosses": result}
333
+ except Exception as e:
334
+ return {"success": False, "error": str(e)}
335
+
336
+
337
+ @mcp.tool()
338
+ def check_overlaps(wkt1: str, wkt2: str) -> dict:
339
+ """Check if two geometries overlap.
340
+
341
+ Args:
342
+ wkt1: WKT of the first geometry
343
+ wkt2: WKT of the second geometry
344
+
345
+ Returns:
346
+ dict: Boolean indicating if geometries overlap
347
+ """
348
+ try:
349
+ geom1 = pygeos.from_wkt(wkt1)
350
+ geom2 = pygeos.from_wkt(wkt2)
351
+ result = bool(pygeos.overlaps(geom1, geom2))
352
+ return {"success": True, "overlaps": result}
353
+ except Exception as e:
354
+ return {"success": False, "error": str(e)}
355
+
356
+
357
+ @mcp.tool()
358
+ def check_disjoint(wkt1: str, wkt2: str) -> dict:
359
+ """Check if two geometries are disjoint (do not interact).
360
+
361
+ Args:
362
+ wkt1: WKT of the first geometry
363
+ wkt2: WKT of the second geometry
364
+
365
+ Returns:
366
+ dict: Boolean indicating if geometries are disjoint
367
+ """
368
+ try:
369
+ geom1 = pygeos.from_wkt(wkt1)
370
+ geom2 = pygeos.from_wkt(wkt2)
371
+ result = bool(pygeos.disjoint(geom1, geom2))
372
+ return {"success": True, "disjoint": result}
373
+ except Exception as e:
374
+ return {"success": False, "error": str(e)}
375
+
376
+
377
+ @mcp.tool()
378
+ def is_valid(wkt: str) -> dict:
379
+ """Check if a geometry is valid.
380
+
381
+ Args:
382
+ wkt: WKT of the geometry
383
+
384
+ Returns:
385
+ dict: Boolean indicating validity and reason if invalid
386
+ """
387
+ try:
388
+ geom = pygeos.from_wkt(wkt)
389
+ valid = bool(pygeos.is_valid(geom))
390
+ reason = pygeos.is_valid_reason(geom) if not valid else None
391
+ return {"success": True, "is_valid": valid, "reason": reason}
392
+ except Exception as e:
393
+ return {"success": False, "error": str(e)}
394
+
395
+
396
+ @mcp.tool()
397
+ def is_simple(wkt: str) -> dict:
398
+ """Check if a geometry is simple (no self-intersection).
399
+
400
+ Args:
401
+ wkt: WKT of the geometry
402
+
403
+ Returns:
404
+ dict: Boolean indicating if geometry is simple
405
+ """
406
+ try:
407
+ geom = pygeos.from_wkt(wkt)
408
+ result = bool(pygeos.is_simple(geom))
409
+ return {"success": True, "is_simple": result}
410
+ except Exception as e:
411
+ return {"success": False, "error": str(e)}
412
+
413
+
414
+ # ==================== Constructive Operations ====================
415
+
416
+ @mcp.tool()
417
+ def buffer_geometry(wkt: str, distance: float, quad_segs: int = 8) -> dict:
418
+ """Create a buffer around a geometry.
419
+
420
+ Args:
421
+ wkt: WKT of the geometry
422
+ distance: Buffer distance (positive for expansion, negative for erosion)
423
+ quad_segs: Number of segments for quarter circle approximation
424
+
425
+ Returns:
426
+ dict: WKT of the buffered geometry
427
+ """
428
+ try:
429
+ geom = pygeos.from_wkt(wkt)
430
+ buffered = pygeos.buffer(geom, distance, quadsegs=quad_segs)
431
+ wkt_result = pygeos.to_wkt(buffered)
432
+ return {"success": True, "wkt": wkt_result}
433
+ except Exception as e:
434
+ return {"success": False, "error": str(e)}
435
+
436
+
437
+ @mcp.tool()
438
+ def simplify_geometry(wkt: str, tolerance: float, preserve_topology: bool = True) -> dict:
439
+ """Simplify a geometry using Douglas-Peucker algorithm.
440
+
441
+ Args:
442
+ wkt: WKT of the geometry
443
+ tolerance: Simplification tolerance
444
+ preserve_topology: Whether to preserve topology
445
+
446
+ Returns:
447
+ dict: WKT of the simplified geometry
448
+ """
449
+ try:
450
+ geom = pygeos.from_wkt(wkt)
451
+ simplified = pygeos.simplify(geom, tolerance, preserve_topology=preserve_topology)
452
+ wkt_result = pygeos.to_wkt(simplified)
453
+ return {"success": True, "wkt": wkt_result}
454
+ except Exception as e:
455
+ return {"success": False, "error": str(e)}
456
+
457
+
458
+ @mcp.tool()
459
+ def get_centroid(wkt: str) -> dict:
460
+ """Get the centroid of a geometry.
461
+
462
+ Args:
463
+ wkt: WKT of the geometry
464
+
465
+ Returns:
466
+ dict: WKT of the centroid point
467
+ """
468
+ try:
469
+ geom = pygeos.from_wkt(wkt)
470
+ centroid = pygeos.centroid(geom)
471
+ wkt_result = pygeos.to_wkt(centroid)
472
+ return {"success": True, "wkt": wkt_result}
473
+ except Exception as e:
474
+ return {"success": False, "error": str(e)}
475
+
476
+
477
+ @mcp.tool()
478
+ def get_convex_hull(wkt: str) -> dict:
479
+ """Get the convex hull of a geometry.
480
+
481
+ Args:
482
+ wkt: WKT of the geometry
483
+
484
+ Returns:
485
+ dict: WKT of the convex hull
486
+ """
487
+ try:
488
+ geom = pygeos.from_wkt(wkt)
489
+ hull = pygeos.convex_hull(geom)
490
+ wkt_result = pygeos.to_wkt(hull)
491
+ return {"success": True, "wkt": wkt_result}
492
+ except Exception as e:
493
+ return {"success": False, "error": str(e)}
494
+
495
+
496
+ @mcp.tool()
497
+ def get_envelope(wkt: str) -> dict:
498
+ """Get the envelope (bounding box) of a geometry as a polygon.
499
+
500
+ Args:
501
+ wkt: WKT of the geometry
502
+
503
+ Returns:
504
+ dict: WKT of the envelope polygon
505
+ """
506
+ try:
507
+ geom = pygeos.from_wkt(wkt)
508
+ envelope = pygeos.envelope(geom)
509
+ wkt_result = pygeos.to_wkt(envelope)
510
+ return {"success": True, "wkt": wkt_result}
511
+ except Exception as e:
512
+ return {"success": False, "error": str(e)}
513
+
514
+
515
+ @mcp.tool()
516
+ def get_boundary(wkt: str) -> dict:
517
+ """Get the boundary of a geometry.
518
+
519
+ Args:
520
+ wkt: WKT of the geometry
521
+
522
+ Returns:
523
+ dict: WKT of the boundary
524
+ """
525
+ try:
526
+ geom = pygeos.from_wkt(wkt)
527
+ boundary = pygeos.boundary(geom)
528
+ wkt_result = pygeos.to_wkt(boundary)
529
+ return {"success": True, "wkt": wkt_result}
530
+ except Exception as e:
531
+ return {"success": False, "error": str(e)}
532
+
533
+
534
+ @mcp.tool()
535
+ def make_valid(wkt: str) -> dict:
536
+ """Make an invalid geometry valid.
537
+
538
+ Args:
539
+ wkt: WKT of the geometry
540
+
541
+ Returns:
542
+ dict: WKT of the valid geometry
543
+ """
544
+ try:
545
+ geom = pygeos.from_wkt(wkt)
546
+ valid_geom = pygeos.make_valid(geom)
547
+ wkt_result = pygeos.to_wkt(valid_geom)
548
+ return {"success": True, "wkt": wkt_result, "is_valid": bool(pygeos.is_valid(valid_geom))}
549
+ except Exception as e:
550
+ return {"success": False, "error": str(e)}
551
+
552
+
553
+ # ==================== Set Operations ====================
554
+
555
+ @mcp.tool()
556
+ def geometry_intersection(wkt1: str, wkt2: str) -> dict:
557
+ """Compute the intersection of two geometries.
558
+
559
+ Args:
560
+ wkt1: WKT of the first geometry
561
+ wkt2: WKT of the second geometry
562
+
563
+ Returns:
564
+ dict: WKT of the intersection
565
+ """
566
+ try:
567
+ geom1 = pygeos.from_wkt(wkt1)
568
+ geom2 = pygeos.from_wkt(wkt2)
569
+ result = pygeos.intersection(geom1, geom2)
570
+ wkt_result = pygeos.to_wkt(result)
571
+ return {"success": True, "wkt": wkt_result, "is_empty": bool(pygeos.is_empty(result))}
572
+ except Exception as e:
573
+ return {"success": False, "error": str(e)}
574
+
575
+
576
+ @mcp.tool()
577
+ def geometry_union(wkt1: str, wkt2: str) -> dict:
578
+ """Compute the union of two geometries.
579
+
580
+ Args:
581
+ wkt1: WKT of the first geometry
582
+ wkt2: WKT of the second geometry
583
+
584
+ Returns:
585
+ dict: WKT of the union
586
  """
587
+ try:
588
+ geom1 = pygeos.from_wkt(wkt1)
589
+ geom2 = pygeos.from_wkt(wkt2)
590
+ result = pygeos.union(geom1, geom2)
591
+ wkt_result = pygeos.to_wkt(result)
592
+ return {"success": True, "wkt": wkt_result}
593
+ except Exception as e:
594
+ return {"success": False, "error": str(e)}
595
 
 
 
596
 
597
+ @mcp.tool()
598
+ def geometry_difference(wkt1: str, wkt2: str) -> dict:
599
+ """Compute the difference of two geometries (wkt1 - wkt2).
600
+
601
+ Args:
602
+ wkt1: WKT of the first geometry
603
+ wkt2: WKT of the geometry to subtract
604
+
605
  Returns:
606
+ dict: WKT of the difference
607
+ """
608
+ try:
609
+ geom1 = pygeos.from_wkt(wkt1)
610
+ geom2 = pygeos.from_wkt(wkt2)
611
+ result = pygeos.difference(geom1, geom2)
612
+ wkt_result = pygeos.to_wkt(result)
613
+ return {"success": True, "wkt": wkt_result, "is_empty": bool(pygeos.is_empty(result))}
614
+ except Exception as e:
615
+ return {"success": False, "error": str(e)}
616
+
617
+
618
+ @mcp.tool()
619
+ def geometry_symmetric_difference(wkt1: str, wkt2: str) -> dict:
620
+ """Compute the symmetric difference of two geometries.
621
+
622
+ Args:
623
+ wkt1: WKT of the first geometry
624
+ wkt2: WKT of the second geometry
625
+
626
+ Returns:
627
+ dict: WKT of the symmetric difference
628
+ """
629
+ try:
630
+ geom1 = pygeos.from_wkt(wkt1)
631
+ geom2 = pygeos.from_wkt(wkt2)
632
+ result = pygeos.symmetric_difference(geom1, geom2)
633
+ wkt_result = pygeos.to_wkt(result)
634
+ return {"success": True, "wkt": wkt_result}
635
+ except Exception as e:
636
+ return {"success": False, "error": str(e)}
637
+
638
+
639
+ # ==================== Linear Operations ====================
640
+
641
+ @mcp.tool()
642
+ def line_interpolate_point(wkt: str, distance: float, normalized: bool = False) -> dict:
643
+ """Interpolate a point along a line at a given distance.
644
+
645
+ Args:
646
+ wkt: WKT of the line geometry
647
+ distance: Distance along the line (or fraction if normalized)
648
+ normalized: If True, distance is treated as fraction (0-1)
649
+
650
+ Returns:
651
+ dict: WKT of the interpolated point
652
+ """
653
+ try:
654
+ geom = pygeos.from_wkt(wkt)
655
+ point = pygeos.line_interpolate_point(geom, distance, normalized=normalized)
656
+ wkt_result = pygeos.to_wkt(point)
657
+ return {"success": True, "wkt": wkt_result}
658
+ except Exception as e:
659
+ return {"success": False, "error": str(e)}
660
+
661
+
662
+ @mcp.tool()
663
+ def line_locate_point(line_wkt: str, point_wkt: str, normalized: bool = False) -> dict:
664
+ """Find the distance along a line to the nearest point.
665
+
666
+ Args:
667
+ line_wkt: WKT of the line geometry
668
+ point_wkt: WKT of the point
669
+ normalized: If True, return fraction (0-1) instead of distance
670
+
671
+ Returns:
672
+ dict: Distance or fraction along the line
673
+ """
674
+ try:
675
+ line = pygeos.from_wkt(line_wkt)
676
+ point = pygeos.from_wkt(point_wkt)
677
+ distance = float(pygeos.line_locate_point(line, point, normalized=normalized))
678
+ return {"success": True, "distance": distance, "normalized": normalized}
679
+ except Exception as e:
680
+ return {"success": False, "error": str(e)}
681
+
682
+
683
+ # ==================== Coordinate Operations ====================
684
+
685
+ @mcp.tool()
686
+ def get_coordinates(wkt: str) -> dict:
687
+ """Extract coordinates from a geometry.
688
+
689
+ Args:
690
+ wkt: WKT of the geometry
691
+
692
+ Returns:
693
+ dict: Array of coordinates
694
+ """
695
+ try:
696
+ geom = pygeos.from_wkt(wkt)
697
+ coords = pygeos.get_coordinates(geom).tolist()
698
+ return {"success": True, "coordinates": coords, "num_coordinates": len(coords)}
699
+ except Exception as e:
700
+ return {"success": False, "error": str(e)}
701
+
702
+
703
+ @mcp.tool()
704
+ def get_num_coordinates(wkt: str) -> dict:
705
+ """Get the number of coordinates in a geometry.
706
+
707
+ Args:
708
+ wkt: WKT of the geometry
709
+
710
+ Returns:
711
+ dict: Number of coordinates
712
+ """
713
+ try:
714
+ geom = pygeos.from_wkt(wkt)
715
+ num = int(pygeos.get_num_coordinates(geom))
716
+ return {"success": True, "num_coordinates": num}
717
+ except Exception as e:
718
+ return {"success": False, "error": str(e)}
719
+
720
+
721
+ # ==================== Utility ====================
722
+
723
+ @mcp.tool()
724
+ def get_pygeos_version() -> dict:
725
+ """Get the PyGEOS library version.
726
+
727
+ Returns:
728
+ dict: Version information
729
  """
730
  try:
 
 
731
  return {
732
  "success": True,
733
+ "pygeos_version": pygeos.__version__,
734
+ "geos_version": pygeos.geos_version_string
735
  }
736
  except Exception as e:
737
+ return {"success": False, "error": str(e)}
738
+
739
+
740
+ def create_app() -> FastMCP:
741
+ """Create and return the FastMCP application instance.
742
+
743
+ Returns:
744
+ FastMCP: The application instance
745
+ """
746
+ return mcp