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.
@@ -1,231 +1,234 @@
1
- """
2
- STAC API search tools for querying Sentinel-2 imagery.
3
-
4
- Uses the EOPF STAC API at https://stac.core.eopf.eodc.eu
5
- """
6
-
7
- from typing import Optional, List
8
- from datetime import datetime, timedelta
9
- from pystac_client import Client
10
-
11
- from ..config import (
12
- STAC_API_URL,
13
- STAC_COLLECTION,
14
- DEFAULT_MAX_CLOUD_COVER,
15
- DEFAULT_MAX_ITEMS,
16
- )
17
-
18
-
19
- def _get_stac_client() -> Client:
20
- """Get a STAC API client instance."""
21
- return Client.open(STAC_API_URL)
22
-
23
-
24
- def _format_item(item) -> dict:
25
- """Format a STAC item into a simplified dictionary."""
26
- props = item.properties
27
-
28
- return {
29
- "id": item.id,
30
- "datetime": props.get("datetime"),
31
- "cloud_cover": props.get("eo:cloud_cover"),
32
- "platform": props.get("platform"),
33
- "bbox": item.bbox,
34
- "geometry": item.geometry,
35
- "assets": {
36
- key: {
37
- "title": asset.title,
38
- "href": asset.href,
39
- "type": asset.media_type,
40
- }
41
- for key, asset in item.assets.items()
42
- if key in ["SR_10m", "SR_20m", "SR_60m", "TCI_10m", "product"]
43
- },
44
- "stac_url": f"{STAC_API_URL}/collections/{STAC_COLLECTION}/items/{item.id}",
45
- }
46
-
47
-
48
- def search_imagery(
49
- bbox: Optional[List[float]] = None,
50
- start_date: Optional[str] = None,
51
- end_date: Optional[str] = None,
52
- max_cloud_cover: Optional[float] = None,
53
- max_items: Optional[int] = None,
54
- ) -> dict:
55
- """
56
- Search for Sentinel-2 L2A imagery in the EOPF STAC catalog.
57
-
58
- Args:
59
- bbox: Bounding box [min_lon, min_lat, max_lon, max_lat]
60
- start_date: Start date in YYYY-MM-DD format
61
- end_date: End date in YYYY-MM-DD format
62
- max_cloud_cover: Maximum cloud cover percentage (0-100)
63
- max_items: Maximum number of items to return
64
-
65
- Returns:
66
- Dictionary with search results including items found
67
-
68
- Example:
69
- >>> search_imagery(
70
- ... bbox=[-74.0, 40.7, -73.9, 40.8],
71
- ... start_date="2024-12-01",
72
- ... end_date="2024-12-20",
73
- ... max_cloud_cover=20
74
- ... )
75
- """
76
- if max_cloud_cover is None:
77
- max_cloud_cover = DEFAULT_MAX_CLOUD_COVER
78
- if max_items is None:
79
- max_items = DEFAULT_MAX_ITEMS
80
-
81
- # Build datetime string
82
- datetime_str = None
83
- if start_date or end_date:
84
- start = start_date or "2015-01-01"
85
- end = end_date or datetime.now().strftime("%Y-%m-%d")
86
- datetime_str = f"{start}/{end}"
87
-
88
- try:
89
- client = _get_stac_client()
90
-
91
- # Build search parameters
92
- search_params = {
93
- "collections": [STAC_COLLECTION],
94
- "max_items": max_items,
95
- }
96
-
97
- if bbox:
98
- search_params["bbox"] = bbox
99
-
100
- if datetime_str:
101
- search_params["datetime"] = datetime_str
102
-
103
- # Execute search
104
- search = client.search(**search_params)
105
- items = list(search.items())
106
-
107
- # Filter by cloud cover (post-filter since API may not support query param)
108
- filtered_items = [
109
- item
110
- for item in items
111
- if item.properties.get("eo:cloud_cover", 100) <= max_cloud_cover
112
- ]
113
-
114
- # Sort by date (newest first)
115
- filtered_items.sort(
116
- key=lambda x: x.properties.get("datetime", ""), reverse=True
117
- )
118
-
119
- # Format results
120
- formatted_items = [_format_item(item) for item in filtered_items]
121
-
122
- return {
123
- "success": True,
124
- "total_found": len(items),
125
- "filtered_count": len(filtered_items),
126
- "items": formatted_items,
127
- "search_params": {
128
- "bbox": bbox,
129
- "datetime": datetime_str,
130
- "max_cloud_cover": max_cloud_cover,
131
- },
132
- }
133
-
134
- except Exception as e:
135
- return {
136
- "success": False,
137
- "error": str(e),
138
- "items": [],
139
- }
140
-
141
-
142
- def get_item_details(item_id: str) -> dict:
143
- """
144
- Get detailed information about a specific STAC item.
145
-
146
- Args:
147
- item_id: The STAC item ID (e.g., "S2B_MSIL2A_20251218T110359_...")
148
-
149
- Returns:
150
- Dictionary with full item details including all assets
151
- """
152
- try:
153
- # Get the item
154
- item_url = f"{STAC_API_URL}/collections/{STAC_COLLECTION}/items/{item_id}"
155
-
156
- import requests
157
-
158
- response = requests.get(item_url)
159
- response.raise_for_status()
160
- item_data = response.json()
161
-
162
- return {
163
- "success": True,
164
- "item": item_data,
165
- }
166
-
167
- except Exception as e:
168
- return {
169
- "success": False,
170
- "error": str(e),
171
- }
172
-
173
-
174
- def list_recent_imagery(
175
- location_name: Optional[str] = None,
176
- days: int = 7,
177
- max_cloud_cover: Optional[float] = None,
178
- max_items: Optional[int] = None,
179
- ) -> dict:
180
- """
181
- List recent Sentinel-2 imagery, optionally for a specific location.
182
-
183
- This is a convenience function that combines geocoding and search.
184
-
185
- Args:
186
- location_name: Optional place name to search around
187
- days: Number of days to look back (default: 7)
188
- max_cloud_cover: Maximum cloud cover percentage
189
- max_items: Maximum items to return
190
-
191
- Returns:
192
- Dictionary with recent imagery items
193
- """
194
- from .geocoding import get_bbox_from_location
195
-
196
- # Calculate date range
197
- end_date = datetime.now()
198
- start_date = end_date - timedelta(days=days)
199
-
200
- # Get bbox if location provided
201
- bbox = None
202
- location_info = None
203
-
204
- if location_name:
205
- bbox_result = get_bbox_from_location(location_name)
206
- if bbox_result["success"]:
207
- bbox = bbox_result["bbox"]
208
- location_info = {
209
- "name": location_name,
210
- "center": bbox_result["center"],
211
- "address": bbox_result["address"],
212
- }
213
- else:
214
- return {
215
- "success": False,
216
- "error": f"Could not geocode location: {location_name}",
217
- }
218
-
219
- # Search for imagery
220
- result = search_imagery(
221
- bbox=bbox,
222
- start_date=start_date.strftime("%Y-%m-%d"),
223
- end_date=end_date.strftime("%Y-%m-%d"),
224
- max_cloud_cover=max_cloud_cover,
225
- max_items=max_items,
226
- )
227
-
228
- if location_info:
229
- result["location"] = location_info
230
-
231
- return result
1
+ """
2
+ STAC API search tools for querying Sentinel-2 imagery.
3
+
4
+ Uses the EOPF STAC API at https://stac.core.eopf.eodc.eu
5
+ """
6
+
7
+ from typing import Optional, List
8
+ from datetime import datetime, timedelta
9
+ from pystac_client import Client
10
+
11
+ from ..config import (
12
+ STAC_API_URL,
13
+ STAC_COLLECTION,
14
+ DEFAULT_MAX_CLOUD_COVER,
15
+ DEFAULT_MAX_ITEMS,
16
+ )
17
+
18
+
19
+ def _get_stac_client() -> Client:
20
+ """Get a STAC API client instance."""
21
+ return Client.open(STAC_API_URL)
22
+
23
+
24
+ def _format_item(item) -> dict:
25
+ """Format a STAC item into a simplified dictionary."""
26
+ props = item.properties
27
+
28
+ return {
29
+ "id": item.id,
30
+ "datetime": props.get("datetime"),
31
+ "cloud_cover": props.get("eo:cloud_cover"),
32
+ "platform": props.get("platform"),
33
+ "bbox": item.bbox,
34
+ "geometry": item.geometry,
35
+ "assets": {
36
+ key: {
37
+ "title": asset.title,
38
+ "href": asset.href,
39
+ "type": asset.media_type,
40
+ }
41
+ for key, asset in item.assets.items()
42
+ if key in ["SR_10m", "SR_20m", "SR_60m", "TCI_10m", "product"]
43
+ },
44
+ "stac_url": f"{STAC_API_URL}/collections/{STAC_COLLECTION}/items/{item.id}",
45
+ }
46
+
47
+
48
+ def search_imagery(
49
+ bbox: Optional[List[float]] = None,
50
+ start_date: Optional[str] = None,
51
+ end_date: Optional[str] = None,
52
+ max_cloud_cover: Optional[float] = None,
53
+ max_items: Optional[int] = None,
54
+ ) -> dict:
55
+ """
56
+ Search for Sentinel-2 L2A imagery in the EOPF STAC catalog.
57
+
58
+ Args:
59
+ bbox: Bounding box [min_lon, min_lat, max_lon, max_lat]
60
+ start_date: Start date in YYYY-MM-DD format
61
+ end_date: End date in YYYY-MM-DD format
62
+ max_cloud_cover: Maximum cloud cover percentage (0-100)
63
+ max_items: Maximum number of items to return
64
+
65
+ Returns:
66
+ Dictionary with search results including items found
67
+
68
+ Example:
69
+ >>> search_imagery(
70
+ ... bbox=[-74.0, 40.7, -73.9, 40.8],
71
+ ... start_date="2024-12-01",
72
+ ... end_date="2024-12-20",
73
+ ... max_cloud_cover=20
74
+ ... )
75
+ """
76
+ if max_cloud_cover is None:
77
+ max_cloud_cover = DEFAULT_MAX_CLOUD_COVER
78
+ if max_items is None:
79
+ max_items = DEFAULT_MAX_ITEMS
80
+
81
+ # Build datetime string
82
+ datetime_str = None
83
+ if start_date or end_date:
84
+ start = start_date or "2015-01-01"
85
+ end = end_date or datetime.now().strftime("%Y-%m-%d")
86
+ datetime_str = f"{start}/{end}"
87
+
88
+ try:
89
+ client = _get_stac_client()
90
+
91
+ # Build search parameters
92
+ search_params = {
93
+ "collections": [STAC_COLLECTION],
94
+ "max_items": max_items,
95
+ }
96
+
97
+ if bbox:
98
+ search_params["bbox"] = bbox
99
+
100
+ if datetime_str:
101
+ search_params["datetime"] = datetime_str
102
+
103
+ # Execute search
104
+ search = client.search(**search_params)
105
+ items = list(search.items())
106
+
107
+ # Filter by cloud cover (post-filter since API may not support query param)
108
+ filtered_items = [
109
+ item
110
+ for item in items
111
+ if item.properties.get("eo:cloud_cover", 100) <= max_cloud_cover
112
+ ]
113
+
114
+ # Sort by date (newest first)
115
+ filtered_items.sort(
116
+ key=lambda x: x.properties.get("datetime", ""), reverse=True
117
+ )
118
+
119
+ # Format results
120
+ formatted_items = [_format_item(item) for item in filtered_items]
121
+
122
+ return {
123
+ "success": True,
124
+ "total_found": len(items),
125
+ "filtered_count": len(filtered_items),
126
+ "items": formatted_items,
127
+ "search_params": {
128
+ "bbox": bbox,
129
+ "datetime": datetime_str,
130
+ "max_cloud_cover": max_cloud_cover,
131
+ },
132
+ }
133
+
134
+ except Exception as e:
135
+ return {
136
+ "success": False,
137
+ "error": str(e),
138
+ "items": [],
139
+ }
140
+
141
+
142
+ def get_item_details(item_id: str) -> dict:
143
+ """
144
+ Get detailed information about a specific STAC item.
145
+
146
+ Args:
147
+ item_id: The STAC item ID (e.g., "S2B_MSIL2A_20251218T110359_...")
148
+
149
+ Returns:
150
+ Dictionary with full item details including all assets
151
+ """
152
+ try:
153
+ client = _get_stac_client()
154
+ collection = client.get_collection(STAC_COLLECTION)
155
+
156
+ # Get the item
157
+ item_url = f"{STAC_API_URL}/collections/{STAC_COLLECTION}/items/{item_id}"
158
+
159
+ import requests
160
+
161
+ response = requests.get(item_url)
162
+ response.raise_for_status()
163
+ item_data = response.json()
164
+
165
+ return {
166
+ "success": True,
167
+ "item": item_data,
168
+ }
169
+
170
+ except Exception as e:
171
+ return {
172
+ "success": False,
173
+ "error": str(e),
174
+ }
175
+
176
+
177
+ def list_recent_imagery(
178
+ location_name: Optional[str] = None,
179
+ days: int = 7,
180
+ max_cloud_cover: Optional[float] = None,
181
+ max_items: Optional[int] = None,
182
+ ) -> dict:
183
+ """
184
+ List recent Sentinel-2 imagery, optionally for a specific location.
185
+
186
+ This is a convenience function that combines geocoding and search.
187
+
188
+ Args:
189
+ location_name: Optional place name to search around
190
+ days: Number of days to look back (default: 7)
191
+ max_cloud_cover: Maximum cloud cover percentage
192
+ max_items: Maximum items to return
193
+
194
+ Returns:
195
+ Dictionary with recent imagery items
196
+ """
197
+ from .geocoding import get_bbox_from_location
198
+
199
+ # Calculate date range
200
+ end_date = datetime.now()
201
+ start_date = end_date - timedelta(days=days)
202
+
203
+ # Get bbox if location provided
204
+ bbox = None
205
+ location_info = None
206
+
207
+ if location_name:
208
+ bbox_result = get_bbox_from_location(location_name)
209
+ if bbox_result["success"]:
210
+ bbox = bbox_result["bbox"]
211
+ location_info = {
212
+ "name": location_name,
213
+ "center": bbox_result["center"],
214
+ "address": bbox_result["address"],
215
+ }
216
+ else:
217
+ return {
218
+ "success": False,
219
+ "error": f"Could not geocode location: {location_name}",
220
+ }
221
+
222
+ # Search for imagery
223
+ result = search_imagery(
224
+ bbox=bbox,
225
+ start_date=start_date.strftime("%Y-%m-%d"),
226
+ end_date=end_date.strftime("%Y-%m-%d"),
227
+ max_cloud_cover=max_cloud_cover,
228
+ max_items=max_items,
229
+ )
230
+
231
+ if location_info:
232
+ result["location"] = location_info
233
+
234
+ return result
@@ -1,85 +1,78 @@
1
- Metadata-Version: 2.4
2
- Name: geomind-ai
3
- Version: 1.0.0
4
- Summary: AI agent for geospatial analysis
5
- Author: Harsh Shinde
6
- License-Expression: MIT
7
- Project-URL: Homepage, https://harshshinde0.github.io/GeoMind/
8
- Project-URL: Repository, https://github.com/HarshShinde0/GeoMind
9
- Project-URL: Documentation, https://github.com/HarshShinde0/GeoMind#readme
10
- Project-URL: Issues, https://github.com/HarshShinde0/GeoMind/issues
11
- Keywords: geospatial,satellite-imagery,sentinel-2,ai-agent,remote-sensing,earth-observation
12
- Classifier: Development Status :: 4 - Beta
13
- Classifier: Intended Audience :: Science/Research
14
- Classifier: Intended Audience :: Developers
15
- Classifier: Programming Language :: Python :: 3.10
16
- Classifier: Programming Language :: Python :: 3.11
17
- Classifier: Programming Language :: Python :: 3.12
18
- Classifier: Topic :: Scientific/Engineering
19
- Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
20
- Requires-Python: >=3.10
21
- Description-Content-Type: text/markdown
22
- License-File: LICENSE
23
- Requires-Dist: openai>=1.0.0
24
- Requires-Dist: pystac-client>=0.8.0
25
- Requires-Dist: pystac>=1.10.0
26
- Requires-Dist: xarray>=2024.1.0
27
- Requires-Dist: zarr>=2.18.0
28
- Requires-Dist: dask>=2024.1.0
29
- Requires-Dist: geopy>=2.4.0
30
- Requires-Dist: fsspec>=2024.1.0
31
- Requires-Dist: aiohttp>=3.9.0
32
- Requires-Dist: requests>=2.31.0
33
- Requires-Dist: s3fs>=2024.1.0
34
- Requires-Dist: matplotlib>=3.8.0
35
- Requires-Dist: numpy>=1.26.0
36
- Requires-Dist: python-dotenv>=1.0.0
37
- Dynamic: license-file
38
-
39
- ### 1. Install Dependencies
40
-
41
- ```bash
42
- pip install -r requirements.txt
43
- ```
44
-
45
- ### 2. Set Up API Key
46
-
47
- Set your HuggingFace API key in the environment or update `config.py`:
48
-
49
- ```python
50
- # In geomind/config.py
51
- HF_API_KEY = "your_huggingface_api_key"
52
- ```
53
-
54
- Get a free API key from [HuggingFace](https://huggingface.co/settings/tokens).
55
-
56
- ### 3. Run the Agent
57
-
58
- ```bash
59
- python main.py
60
- ```
61
-
62
- ## Example Queries
63
-
64
- ```
65
-
66
- 💬 "Create an RGB composite for the most recent image of London"
67
-
68
- 💬 "Calculate NDVI for Central Park, New York"
69
-
70
- 💬 "What images are available for Tokyo with less than 10% cloud cover?"
71
- ```
72
-
73
- ## Approach
74
-
75
- ### Traditional Approach
76
- ```
77
- Full Scene Download → Local Storage → Process → Result
78
- ~720 MB Disk I/O Slow
79
- ```
80
-
81
- ### GeoMind Approach (Zarr + fsspec)
82
- ```
83
- HTTP Range Request → Stream Chunks → Process in Memory → Result
84
- ~1-5 MB No disk Fast
85
- ```
1
+ Metadata-Version: 2.4
2
+ Name: geomind-ai
3
+ Version: 1.0.2
4
+ Summary: AI agent for geospatial analysis with Sentinel-2 satellite imagery
5
+ Author: Harsh Shinde
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/HarshShinde0/GeoMind
8
+ Project-URL: Repository, https://github.com/HarshShinde0/GeoMind
9
+ Project-URL: Documentation, https://github.com/HarshShinde0/GeoMind#readme
10
+ Project-URL: Issues, https://github.com/HarshShinde0/GeoMind/issues
11
+ Keywords: geospatial,satellite-imagery,sentinel-2,ai-agent,remote-sensing,earth-observation
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Topic :: Scientific/Engineering
19
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
20
+ Requires-Python: >=3.10
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Requires-Dist: openai>=1.0.0
24
+ Requires-Dist: pystac-client>=0.8.0
25
+ Requires-Dist: pystac>=1.10.0
26
+ Requires-Dist: xarray>=2024.1.0
27
+ Requires-Dist: zarr>=2.18.0
28
+ Requires-Dist: dask>=2024.1.0
29
+ Requires-Dist: geopy>=2.4.0
30
+ Requires-Dist: fsspec>=2024.1.0
31
+ Requires-Dist: aiohttp>=3.9.0
32
+ Requires-Dist: requests>=2.31.0
33
+ Requires-Dist: s3fs>=2024.1.0
34
+ Requires-Dist: matplotlib>=3.8.0
35
+ Requires-Dist: numpy>=1.26.0
36
+ Requires-Dist: python-dotenv>=1.0.0
37
+ Dynamic: license-file
38
+
39
+ ### Install Dependencies
40
+
41
+ ```bash
42
+ pip install -r requirements.txt
43
+ ```
44
+
45
+ ### Run the Agent
46
+
47
+ ```bash
48
+ # Interactive mode
49
+ geomind
50
+
51
+ # Single query
52
+ geomind --query "Find recent imagery of Paris"
53
+ ```
54
+
55
+ ## Example Queries
56
+
57
+ ```
58
+
59
+ 💬 "Create an RGB composite for the most recent image of London"
60
+
61
+ 💬 "Calculate NDVI for Central Park, New York"
62
+
63
+ 💬 "What images are available for Tokyo with less than 10% cloud cover?"
64
+ ```
65
+
66
+ ## Approach
67
+
68
+ ### Traditional Approach
69
+ ```
70
+ Full Scene Download Local Storage Process Result
71
+ ~720 MB Disk I/O Slow
72
+ ```
73
+
74
+ ### GeoMind Approach (Zarr + fsspec)
75
+ ```
76
+ HTTP Range Request → Stream Chunks → Process in Memory → Result
77
+ ~1-5 MB No disk Fast
78
+ ```
@@ -0,0 +1,14 @@
1
+ geomind/__init__.py,sha256=mma_vqNjiOpc0WOvclIdFqULWUnc7P8cDfAfqkPOsd4,151
2
+ geomind/agent.py,sha256=iE-mwRLuA5sn4YtCQYU30GxcwsJoDhbhxdUrYsUJU0c,15010
3
+ geomind/cli.py,sha256=tmMkvO9scyvzpRtiKbcb1jtWn3fSgFoywQmuusmjDuI,3283
4
+ geomind/config.py,sha256=hv3DNM7QbCuOSWuPmXeiVxEbGxH7fRLxOc8jexd7rrY,2011
5
+ geomind/tools/__init__.py,sha256=8iumGwIFHh8Bj1VJNgZtmKnEBqCy6_cRkzYENDUH7x4,720
6
+ geomind/tools/geocoding.py,sha256=hiLpzHpkJP6IgWAUtZMnHL6qpkWcYWVLpGe0yfYxXv8,3007
7
+ geomind/tools/processing.py,sha256=vMp8PMb8h8QiBRBFRvI_TGRqEDBTDQvSV0zvC1Ji5bc,9976
8
+ geomind/tools/stac_search.py,sha256=V6230l4aHjedPWXu-3Cjmfc6diSFh5zsycewUko0W8k,6452
9
+ geomind_ai-1.0.2.dist-info/licenses/LICENSE,sha256=aveu0ERm7I3NnIu8rtpKdvd0eyRpmktXKU0PBABtSN0,1069
10
+ geomind_ai-1.0.2.dist-info/METADATA,sha256=tF0kr6M6J_Vo1_RiU3sAqseL_76xU2rvJUT52yej8Mw,2233
11
+ geomind_ai-1.0.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
12
+ geomind_ai-1.0.2.dist-info/entry_points.txt,sha256=2nPR3faYKl0-1epccvzMJ2xdi-Q1Vt7aOSvA84oIWnw,45
13
+ geomind_ai-1.0.2.dist-info/top_level.txt,sha256=rjKWNSNRhq4R9xJoZGsG-eAaH7BmTVNvfrrbcaJMIIs,8
14
+ geomind_ai-1.0.2.dist-info/RECORD,,