geomind-ai 1.0.0__py3-none-any.whl → 1.0.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
geomind/cli.py CHANGED
@@ -1,125 +1,125 @@
1
- """
2
- Command-line interface for GeoMind.
3
- """
4
-
5
- import sys
6
- import argparse
7
- from typing import Optional
8
-
9
- from .agent import GeoMindAgent
10
-
11
-
12
- def main():
13
- """Main CLI entry point for the geomind package."""
14
- parser = argparse.ArgumentParser(
15
- description="GeoMind - AI agent for geospatial analysis with Sentinel-2 imagery",
16
- formatter_class=argparse.RawDescriptionHelpFormatter,
17
- epilog="""
18
- Examples:
19
- # Interactive mode
20
- geomind
21
-
22
- # Single query
23
- geomind --query "Find recent imagery of Paris"
24
-
25
- # With custom model
26
- geomind --model "anthropic/claude-3.5-sonnet"
27
-
28
- # With API key
29
- geomind --api-key "your-openrouter-api-key"
30
-
31
- Environment Variables:
32
- OPENROUTER_API_KEY Your OpenRouter API key
33
- OPENROUTER_MODEL Model to use (default: xiaomi/mimo-v2-flash:free)
34
- OPENROUTER_API_URL API endpoint (default: https://openrouter.ai/api/v1)
35
- """,
36
- )
37
-
38
- parser.add_argument(
39
- "--query",
40
- "-q",
41
- type=str,
42
- help="Single query to run (if not provided, starts interactive mode)",
43
- )
44
- parser.add_argument(
45
- "--model",
46
- "-m",
47
- type=str,
48
- help="Model name to use (e.g., 'anthropic/claude-3.5-sonnet')",
49
- )
50
- parser.add_argument(
51
- "--api-key",
52
- "-k",
53
- type=str,
54
- help="OpenRouter API key (or set OPENROUTER_API_KEY env variable)",
55
- )
56
- parser.add_argument(
57
- "--version", "-v", action="store_true", help="Show version and exit"
58
- )
59
-
60
- args = parser.parse_args()
61
-
62
- if args.version:
63
- from . import __version__
64
-
65
- print(f"GeoMind version {__version__}")
66
- sys.exit(0)
67
-
68
- # Start interactive or single-query mode
69
- try:
70
- if args.query:
71
- # Single query mode
72
- agent = GeoMindAgent(model=args.model, api_key=args.api_key)
73
- agent.chat(args.query)
74
- else:
75
- # Interactive mode
76
- run_interactive(model=args.model, api_key=args.api_key)
77
- except ValueError as e:
78
- print(f"\n❌ Error: {e}")
79
- sys.exit(1)
80
- except KeyboardInterrupt:
81
- print("\n\n👋 Goodbye!")
82
- sys.exit(0)
83
- except Exception as e:
84
- print(f"\n❌ Unexpected error: {e}")
85
- sys.exit(1)
86
-
87
-
88
- def run_interactive(model: Optional[str] = None, api_key: Optional[str] = None):
89
- """Run interactive CLI mode."""
90
- print("=" * 60)
91
- print("🌍 GeoMind - Geospatial AI Agent")
92
- print("=" * 60)
93
- print("Powered by OpenRouter | Sentinel-2 Imagery")
94
- print("Type 'quit' or 'exit' to end the session")
95
- print("Type 'reset' to start a new conversation")
96
- print("=" * 60)
97
-
98
- agent = GeoMindAgent(model=model, api_key=api_key)
99
-
100
- while True:
101
- try:
102
- user_input = input("\n💬 You: ").strip()
103
-
104
- if not user_input:
105
- continue
106
-
107
- if user_input.lower() in ["quit", "exit", "q"]:
108
- print("\n👋 Goodbye!")
109
- break
110
-
111
- if user_input.lower() == "reset":
112
- agent.reset()
113
- continue
114
-
115
- agent.chat(user_input)
116
-
117
- except KeyboardInterrupt:
118
- print("\n\n👋 Goodbye!")
119
- break
120
- except Exception as e:
121
- print(f"\n❌ Error: {e}")
122
-
123
-
124
- if __name__ == "__main__":
125
- main()
1
+ """
2
+ Command-line interface for GeoMind.
3
+ """
4
+
5
+ import sys
6
+ import argparse
7
+ from typing import Optional
8
+
9
+ from .agent import GeoMindAgent
10
+
11
+
12
+ def main():
13
+ """Main CLI entry point for the geomind package."""
14
+ parser = argparse.ArgumentParser(
15
+ description="GeoMind - AI agent for geospatial analysis with Sentinel-2 imagery",
16
+ formatter_class=argparse.RawDescriptionHelpFormatter,
17
+ epilog="""
18
+ Examples:
19
+ # Interactive mode
20
+ geomind
21
+
22
+ # Single query
23
+ geomind --query "Find recent imagery of Paris"
24
+
25
+ # With custom model
26
+ geomind --model "anthropic/claude-3.5-sonnet"
27
+
28
+ # With API key
29
+ geomind --api-key "your-openrouter-api-key"
30
+
31
+ Environment Variables:
32
+ OPENROUTER_API_KEY Your OpenRouter API key
33
+ OPENROUTER_MODEL Model to use (default: xiaomi/mimo-v2-flash:free)
34
+ OPENROUTER_API_URL API endpoint (default: https://openrouter.ai/api/v1)
35
+ """,
36
+ )
37
+
38
+ parser.add_argument(
39
+ "--query",
40
+ "-q",
41
+ type=str,
42
+ help="Single query to run (if not provided, starts interactive mode)",
43
+ )
44
+ parser.add_argument(
45
+ "--model",
46
+ "-m",
47
+ type=str,
48
+ help="Model name to use (e.g., 'anthropic/claude-3.5-sonnet')",
49
+ )
50
+ parser.add_argument(
51
+ "--api-key",
52
+ "-k",
53
+ type=str,
54
+ help="OpenRouter API key (or set OPENROUTER_API_KEY env variable)",
55
+ )
56
+ parser.add_argument(
57
+ "--version", "-v", action="store_true", help="Show version and exit"
58
+ )
59
+
60
+ args = parser.parse_args()
61
+
62
+ if args.version:
63
+ from . import __version__
64
+
65
+ print(f"GeoMind version {__version__}")
66
+ sys.exit(0)
67
+
68
+ # Start interactive or single-query mode
69
+ try:
70
+ if args.query:
71
+ # Single query mode
72
+ agent = GeoMindAgent(model=args.model, api_key=args.api_key)
73
+ agent.chat(args.query)
74
+ else:
75
+ # Interactive mode
76
+ run_interactive(model=args.model, api_key=args.api_key)
77
+ except ValueError as e:
78
+ print(f"\n❌ Error: {e}")
79
+ sys.exit(1)
80
+ except KeyboardInterrupt:
81
+ print("\n\n👋 Goodbye!")
82
+ sys.exit(0)
83
+ except Exception as e:
84
+ print(f"\n❌ Unexpected error: {e}")
85
+ sys.exit(1)
86
+
87
+
88
+ def run_interactive(model: Optional[str] = None, api_key: Optional[str] = None):
89
+ """Run interactive CLI mode."""
90
+ print("=" * 60)
91
+ print("🌍 GeoMind - Geospatial AI Agent")
92
+ print("=" * 60)
93
+ print("Powered by OpenRouter | Sentinel-2 Imagery")
94
+ print("Type 'quit' or 'exit' to end the session")
95
+ print("Type 'reset' to start a new conversation")
96
+ print("=" * 60)
97
+
98
+ agent = GeoMindAgent(model=model, api_key=api_key)
99
+
100
+ while True:
101
+ try:
102
+ user_input = input("\n💬 You: ").strip()
103
+
104
+ if not user_input:
105
+ continue
106
+
107
+ if user_input.lower() in ["quit", "exit", "q"]:
108
+ print("\n👋 Goodbye!")
109
+ break
110
+
111
+ if user_input.lower() == "reset":
112
+ agent.reset()
113
+ continue
114
+
115
+ agent.chat(user_input)
116
+
117
+ except KeyboardInterrupt:
118
+ print("\n\n👋 Goodbye!")
119
+ break
120
+ except Exception as e:
121
+ print(f"\n❌ Error: {e}")
122
+
123
+
124
+ if __name__ == "__main__":
125
+ main()
geomind/config.py CHANGED
@@ -1,55 +1,55 @@
1
- """
2
- Configuration settings for GeoMind agent.
3
- """
4
-
5
- import os
6
- from pathlib import Path
7
- from dotenv import load_dotenv
8
-
9
- # Load environment variables from .env file
10
- load_dotenv()
11
-
12
- # STAC API Configuration
13
- STAC_API_URL = "https://stac.core.eopf.eodc.eu"
14
- STAC_COLLECTION = "sentinel-2-l2a"
15
-
16
- # Sentinel-2 Band Configuration
17
- SENTINEL2_BANDS = {
18
- "B01": {"name": "Coastal aerosol", "wavelength": 0.443, "resolution": 60},
19
- "B02": {"name": "Blue", "wavelength": 0.490, "resolution": 10},
20
- "B03": {"name": "Green", "wavelength": 0.560, "resolution": 10},
21
- "B04": {"name": "Red", "wavelength": 0.665, "resolution": 10},
22
- "B05": {"name": "Red Edge 1", "wavelength": 0.704, "resolution": 20},
23
- "B06": {"name": "Red Edge 2", "wavelength": 0.740, "resolution": 20},
24
- "B07": {"name": "Red Edge 3", "wavelength": 0.783, "resolution": 20},
25
- "B08": {"name": "NIR", "wavelength": 0.842, "resolution": 10},
26
- "B8A": {"name": "NIR Narrow", "wavelength": 0.865, "resolution": 20},
27
- "B09": {"name": "Water Vapour", "wavelength": 0.945, "resolution": 60},
28
- "B11": {"name": "SWIR 1", "wavelength": 1.610, "resolution": 20},
29
- "B12": {"name": "SWIR 2", "wavelength": 2.190, "resolution": 20},
30
- }
31
-
32
- # Reflectance scale and offset (from STAC metadata)
33
- REFLECTANCE_SCALE = 0.0001
34
- REFLECTANCE_OFFSET = -0.1
35
-
36
- # RGB Band Mapping
37
- RGB_BANDS = {"red": "b04", "green": "b03", "blue": "b02"}
38
-
39
- # Default search parameters
40
- DEFAULT_MAX_CLOUD_COVER = 20 # percent
41
- DEFAULT_BUFFER_KM = 10 # km buffer around point for bbox
42
- DEFAULT_MAX_ITEMS = 10
43
-
44
- # Output directory for saved images
45
- OUTPUT_DIR = Path("outputs")
46
- OUTPUT_DIR.mkdir(exist_ok=True)
47
-
48
- # Geocoding configuration
49
- GEOCODER_USER_AGENT = "geomind_agent_v0.1"
50
-
51
- # OpenRouter API Configuration
52
- # OpenRouter provides access to multiple AI models via API
53
- OPENROUTER_API_KEY = os.getenv("OPENROUTER_API_KEY")
54
- OPENROUTER_API_URL = os.getenv("OPENROUTER_API_URL", "https://openrouter.ai/api/v1")
55
- OPENROUTER_MODEL = os.getenv("OPENROUTER_MODEL", "xiaomi/mimo-v2-flash:free")
1
+ """
2
+ Configuration settings for GeoMind agent.
3
+ """
4
+
5
+ import os
6
+ from pathlib import Path
7
+ from dotenv import load_dotenv
8
+
9
+ # Load environment variables from .env file
10
+ load_dotenv()
11
+
12
+ # STAC API Configuration
13
+ STAC_API_URL = "https://stac.core.eopf.eodc.eu"
14
+ STAC_COLLECTION = "sentinel-2-l2a"
15
+
16
+ # Sentinel-2 Band Configuration
17
+ SENTINEL2_BANDS = {
18
+ "B01": {"name": "Coastal aerosol", "wavelength": 0.443, "resolution": 60},
19
+ "B02": {"name": "Blue", "wavelength": 0.490, "resolution": 10},
20
+ "B03": {"name": "Green", "wavelength": 0.560, "resolution": 10},
21
+ "B04": {"name": "Red", "wavelength": 0.665, "resolution": 10},
22
+ "B05": {"name": "Red Edge 1", "wavelength": 0.704, "resolution": 20},
23
+ "B06": {"name": "Red Edge 2", "wavelength": 0.740, "resolution": 20},
24
+ "B07": {"name": "Red Edge 3", "wavelength": 0.783, "resolution": 20},
25
+ "B08": {"name": "NIR", "wavelength": 0.842, "resolution": 10},
26
+ "B8A": {"name": "NIR Narrow", "wavelength": 0.865, "resolution": 20},
27
+ "B09": {"name": "Water Vapour", "wavelength": 0.945, "resolution": 60},
28
+ "B11": {"name": "SWIR 1", "wavelength": 1.610, "resolution": 20},
29
+ "B12": {"name": "SWIR 2", "wavelength": 2.190, "resolution": 20},
30
+ }
31
+
32
+ # Reflectance scale and offset (from STAC metadata)
33
+ REFLECTANCE_SCALE = 0.0001
34
+ REFLECTANCE_OFFSET = -0.1
35
+
36
+ # RGB Band Mapping
37
+ RGB_BANDS = {"red": "b04", "green": "b03", "blue": "b02"}
38
+
39
+ # Default search parameters
40
+ DEFAULT_MAX_CLOUD_COVER = 20 # percent
41
+ DEFAULT_BUFFER_KM = 10 # km buffer around point for bbox
42
+ DEFAULT_MAX_ITEMS = 10
43
+
44
+ # Output directory for saved images
45
+ OUTPUT_DIR = Path("outputs")
46
+ OUTPUT_DIR.mkdir(exist_ok=True)
47
+
48
+ # Geocoding configuration
49
+ GEOCODER_USER_AGENT = "geomind_agent_v0.1"
50
+
51
+ # OpenRouter API Configuration
52
+ # OpenRouter provides access to multiple AI models via API
53
+ OPENROUTER_API_KEY = os.getenv("OPENROUTER_API_KEY")
54
+ OPENROUTER_API_URL = os.getenv("OPENROUTER_API_URL", "https://openrouter.ai/api/v1")
55
+ OPENROUTER_MODEL = os.getenv("OPENROUTER_MODEL", "xiaomi/mimo-v2-flash:free")
geomind/tools/__init__.py CHANGED
@@ -1,27 +1,27 @@
1
- """
2
- GeoMind Tools - Functions available to the AI agent.
3
-
4
- This module contains all the tools that the agent can call:
5
- - Geocoding: Convert place names to coordinates
6
- - STAC Search: Query the Sentinel-2 catalog
7
- - Processing: Load and process imagery data
8
- """
9
-
10
- from .geocoding import geocode_location, get_bbox_from_location
11
- from .stac_search import search_imagery, get_item_details, list_recent_imagery
12
- from .processing import (
13
- create_rgb_composite,
14
- calculate_ndvi,
15
- get_band_statistics,
16
- )
17
-
18
- __all__ = [
19
- "geocode_location",
20
- "get_bbox_from_location",
21
- "search_imagery",
22
- "get_item_details",
23
- "list_recent_imagery",
24
- "create_rgb_composite",
25
- "calculate_ndvi",
26
- "get_band_statistics",
27
- ]
1
+ """
2
+ GeoMind Tools - Functions available to the AI agent.
3
+
4
+ This module contains all the tools that the agent can call:
5
+ - Geocoding: Convert place names to coordinates
6
+ - STAC Search: Query the Sentinel-2 catalog
7
+ - Processing: Load and process imagery data
8
+ """
9
+
10
+ from .geocoding import geocode_location, get_bbox_from_location
11
+ from .stac_search import search_imagery, get_item_details, list_recent_imagery
12
+ from .processing import (
13
+ create_rgb_composite,
14
+ calculate_ndvi,
15
+ get_band_statistics,
16
+ )
17
+
18
+ __all__ = [
19
+ "geocode_location",
20
+ "get_bbox_from_location",
21
+ "search_imagery",
22
+ "get_item_details",
23
+ "list_recent_imagery",
24
+ "create_rgb_composite",
25
+ "calculate_ndvi",
26
+ "get_band_statistics",
27
+ ]
@@ -1,108 +1,108 @@
1
- """
2
- Geocoding tools for converting place names to coordinates.
3
-
4
- Uses OpenStreetMap's Nominatim service via geopy.
5
- """
6
-
7
- from typing import Optional
8
- from geopy.geocoders import Nominatim
9
- from geopy.extra.rate_limiter import RateLimiter
10
-
11
- from ..config import GEOCODER_USER_AGENT, DEFAULT_BUFFER_KM
12
-
13
-
14
- def geocode_location(place_name: str) -> dict:
15
- """
16
- Convert a place name to geographic coordinates.
17
-
18
- Args:
19
- place_name: Name of the location (e.g., "New York", "Paris, France")
20
-
21
- Returns:
22
- Dictionary with latitude, longitude, and full address
23
-
24
- Example:
25
- >>> geocode_location("Central Park, New York")
26
- {'latitude': 40.7828, 'longitude': -73.9653, 'address': '...'}
27
- """
28
- geolocator = Nominatim(user_agent=GEOCODER_USER_AGENT, timeout=10)
29
- geocode = RateLimiter(geolocator.geocode, min_delay_seconds=1)
30
-
31
- location = geocode(place_name)
32
-
33
- if location is None:
34
- return {
35
- "success": False,
36
- "error": f"Could not find location: {place_name}",
37
- "latitude": None,
38
- "longitude": None,
39
- "address": None,
40
- }
41
-
42
- return {
43
- "success": True,
44
- "latitude": location.latitude,
45
- "longitude": location.longitude,
46
- "address": location.address,
47
- }
48
-
49
-
50
- def get_bbox_from_location(place_name: str, buffer_km: Optional[float] = None) -> dict:
51
- """
52
- Convert a place name to a bounding box suitable for STAC queries.
53
-
54
- Creates a square bounding box centered on the location with the
55
- specified buffer distance.
56
-
57
- Args:
58
- place_name: Name of the location (e.g., "San Francisco")
59
- buffer_km: Buffer distance in kilometers (default: 10km)
60
-
61
- Returns:
62
- Dictionary with bbox [min_lon, min_lat, max_lon, max_lat] and center point
63
-
64
- Example:
65
- >>> get_bbox_from_location("London", buffer_km=5)
66
- {'bbox': [-0.17, 51.46, -0.08, 51.55], 'center': {...}}
67
- """
68
- if buffer_km is None:
69
- buffer_km = DEFAULT_BUFFER_KM
70
-
71
- # Get coordinates
72
- location_result = geocode_location(place_name)
73
-
74
- if not location_result["success"]:
75
- return {
76
- "success": False,
77
- "error": location_result["error"],
78
- "bbox": None,
79
- }
80
-
81
- lat = location_result["latitude"]
82
- lon = location_result["longitude"]
83
-
84
- # Calculate approximate degree offset
85
- # 1 degree latitude ≈ 111 km
86
- # 1 degree longitude ≈ 111 * cos(latitude) km
87
- import math
88
-
89
- lat_offset = buffer_km / 111.0
90
- lon_offset = buffer_km / (111.0 * math.cos(math.radians(lat)))
91
-
92
- bbox = [
93
- lon - lon_offset, # min_lon (west)
94
- lat - lat_offset, # min_lat (south)
95
- lon + lon_offset, # max_lon (east)
96
- lat + lat_offset, # max_lat (north)
97
- ]
98
-
99
- return {
100
- "success": True,
101
- "bbox": bbox,
102
- "center": {
103
- "latitude": lat,
104
- "longitude": lon,
105
- },
106
- "address": location_result["address"],
107
- "buffer_km": buffer_km,
108
- }
1
+ """
2
+ Geocoding tools for converting place names to coordinates.
3
+
4
+ Uses OpenStreetMap's Nominatim service via geopy.
5
+ """
6
+
7
+ from typing import Optional
8
+ from geopy.geocoders import Nominatim
9
+ from geopy.extra.rate_limiter import RateLimiter
10
+
11
+ from ..config import GEOCODER_USER_AGENT, DEFAULT_BUFFER_KM
12
+
13
+
14
+ def geocode_location(place_name: str) -> dict:
15
+ """
16
+ Convert a place name to geographic coordinates.
17
+
18
+ Args:
19
+ place_name: Name of the location (e.g., "New York", "Paris, France")
20
+
21
+ Returns:
22
+ Dictionary with latitude, longitude, and full address
23
+
24
+ Example:
25
+ >>> geocode_location("Central Park, New York")
26
+ {'latitude': 40.7828, 'longitude': -73.9653, 'address': '...'}
27
+ """
28
+ geolocator = Nominatim(user_agent=GEOCODER_USER_AGENT, timeout=10)
29
+ geocode = RateLimiter(geolocator.geocode, min_delay_seconds=1)
30
+
31
+ location = geocode(place_name)
32
+
33
+ if location is None:
34
+ return {
35
+ "success": False,
36
+ "error": f"Could not find location: {place_name}",
37
+ "latitude": None,
38
+ "longitude": None,
39
+ "address": None,
40
+ }
41
+
42
+ return {
43
+ "success": True,
44
+ "latitude": location.latitude,
45
+ "longitude": location.longitude,
46
+ "address": location.address,
47
+ }
48
+
49
+
50
+ def get_bbox_from_location(place_name: str, buffer_km: Optional[float] = None) -> dict:
51
+ """
52
+ Convert a place name to a bounding box suitable for STAC queries.
53
+
54
+ Creates a square bounding box centered on the location with the
55
+ specified buffer distance.
56
+
57
+ Args:
58
+ place_name: Name of the location (e.g., "San Francisco")
59
+ buffer_km: Buffer distance in kilometers (default: 10km)
60
+
61
+ Returns:
62
+ Dictionary with bbox [min_lon, min_lat, max_lon, max_lat] and center point
63
+
64
+ Example:
65
+ >>> get_bbox_from_location("London", buffer_km=5)
66
+ {'bbox': [-0.17, 51.46, -0.08, 51.55], 'center': {...}}
67
+ """
68
+ if buffer_km is None:
69
+ buffer_km = DEFAULT_BUFFER_KM
70
+
71
+ # Get coordinates
72
+ location_result = geocode_location(place_name)
73
+
74
+ if not location_result["success"]:
75
+ return {
76
+ "success": False,
77
+ "error": location_result["error"],
78
+ "bbox": None,
79
+ }
80
+
81
+ lat = location_result["latitude"]
82
+ lon = location_result["longitude"]
83
+
84
+ # Calculate approximate degree offset
85
+ # 1 degree latitude ≈ 111 km
86
+ # 1 degree longitude ≈ 111 * cos(latitude) km
87
+ import math
88
+
89
+ lat_offset = buffer_km / 111.0
90
+ lon_offset = buffer_km / (111.0 * math.cos(math.radians(lat)))
91
+
92
+ bbox = [
93
+ lon - lon_offset, # min_lon (west)
94
+ lat - lat_offset, # min_lat (south)
95
+ lon + lon_offset, # max_lon (east)
96
+ lat + lat_offset, # max_lat (north)
97
+ ]
98
+
99
+ return {
100
+ "success": True,
101
+ "bbox": bbox,
102
+ "center": {
103
+ "latitude": lat,
104
+ "longitude": lon,
105
+ },
106
+ "address": location_result["address"],
107
+ "buffer_km": buffer_km,
108
+ }