JulioContrerasH commited on
Commit
caf3c39
1 Parent(s): d2a4d5e

Add Git LFS tracking for large files

Browse files
Dockerfile ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.11
2
+
3
+ # Set up a new user named "user" with user ID 1000 for permission
4
+ RUN useradd -m -u 1000 user
5
+ # Switch to the "user" user
6
+ USER user
7
+ # Set home to the user's home directory
8
+ ENV HOME=/home/user \
9
+ PATH=/home/user/.local/bin:$PATH
10
+
11
+ # Upgreade pip
12
+ RUN pip install --no-cache-dir --upgrade pip
13
+
14
+ COPY --chown=user requirements.txt .
15
+
16
+ # Install requirements
17
+ RUN pip install --no-cache-dir --upgrade -r requirements.txt
18
+
19
+ COPY --chown=user *.py *.css /
20
+ COPY --chown=user helpers/* /helpers/
21
+
22
+ ENTRYPOINT ["solara", "run", "app.py", "--host=0.0.0.0", "--port", "7860", "--production"]
README.md CHANGED
@@ -1,10 +1,11 @@
1
  ---
2
- title: Example1
3
- emoji:
4
- colorFrom: pink
5
- colorTo: indigo
6
  sdk: docker
7
  pinned: false
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: Major TOM Viewer
3
+ emoji: 🌍
4
+ colorFrom: gray
5
+ colorTo: gray
6
  sdk: docker
7
  pinned: false
8
+ short_description: Quick View of Samples in the MajorTOM-Core Dataset
9
  ---
10
 
11
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
app.py ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sys
2
+ import os
3
+ import leafmap
4
+ from helpers.helpers_grid import *
5
+ from helpers.helpers_functional import *
6
+ from leafmap.toolbar import change_basemap
7
+ from IPython.display import display
8
+ import ipywidgets
9
+ import solara
10
+
11
+ instructions_top = '''
12
+ ![image/png](https://cdn-uploads.huggingface.co/production/uploads/6304c06eeb6d777a838eab63/BJKsLwX0GG4W3-gdf40TJ.png)
13
+ # Dataset Viewer
14
+ This app provides a way of exploring samples present in the MajorTOM-Core dataset.
15
+ It contains nearly every piece of Earth captured by ESA Sentinel-2 satellite as well as a large fraction of paired Sentinel-1 data from a similar time period.
16
+ ### Instructions
17
+ To find a sample, navigate on the map to a place of interest. Click `Find Sample` to find a dataset sample that contains the central pixel of your current view.
18
+ '''
19
+
20
+ instructions_bottom = '''
21
+ <details><summary><strong>🏝 Couldn't find a sample? See this figure of global coverage:</strong></summary>
22
+ <img src='https://cdn-uploads.huggingface.co/production/uploads/6304c06eeb6d777a838eab63/2KTarfsM0a1dNYEbXriUH.png' />
23
+ </details>
24
+ '''
25
+
26
+ image_data = solara.reactive(Image.new('RGB',(1068,1068)))
27
+ center = solara.reactive((20, 0))
28
+ gridcell = solara.reactive('')
29
+ timestamp = solara.reactive('')
30
+ zoom= solara.reactive(4)
31
+ source = solara.reactive('S2-L2A')
32
+
33
+ @solara.component
34
+ def Page():
35
+ with solara.Column():
36
+
37
+ with solara.Card(margin=10):
38
+ solara.Markdown(instructions_top)
39
+ solara.Button(label="Website",
40
+ icon_name="mdi-map-legend",
41
+ attributes={"href": 'https://www.huggingface.co/Major-TOM', "target": "_blank"},
42
+ text=True,
43
+ outlined=True)
44
+
45
+ solara.Button(label="arXiv Paper",
46
+ icon_name="mdi-script-text",
47
+ attributes={"href": 'https://www.arxiv.org/abs/2402.12095', "target": "_blank"},
48
+ text=True,
49
+ outlined=True)
50
+ solara.Markdown(instructions_bottom)
51
+
52
+ def update_image():
53
+ ret = map_to_image(m, return_centre=True, return_gridcell=True, return_timestamp=True, source=source.value)
54
+ if ret is not None:
55
+ image_data.value, center.value, gridcell.value, timestamp.value = ret
56
+ zoom.value=12
57
+ else:
58
+ image_data.value = Image.new('RGB',(1068,1068))
59
+ center.value = (20,0)
60
+ zoom.value = 4
61
+
62
+ def update_source(val):
63
+ source.value = val
64
+ update_image()
65
+
66
+ with solara.Card(margin=10):
67
+ with solara.ColumnsResponsive(default=12, small=12, medium=6, large=6, xlarge=6):
68
+ with solara.Column(align='center'):
69
+ m = leafmap.Map(
70
+ height=560,
71
+ width=560,
72
+ layout=ipywidgets.Layout(max_width='60vw', max_height='80vh'),
73
+ zoom=zoom.value,
74
+ center=center.value,
75
+ draw_control=False,
76
+ measure_control=False,
77
+ fullscreen_control=False,
78
+ toolbar_control=False,
79
+ attribution_control=True,
80
+ )
81
+ display(m)
82
+ button = solara.Button("Find Sample", on_click=update_image, height='80px')
83
+ solara.Select(label="Processing Level", value=source, values=['S2-L2A','S2-L1C', 'S1-RTC'], on_value=update_source)
84
+ with solara.Column(align='center'):
85
+ output = solara.Image(image_data.value)
86
+ solara.Markdown('''
87
+ | | |
88
+ |--------------------:|:----:|
89
+ | Latitude, Longitude | {:.3f}, {:.3f} |
90
+ | MajorTOM Grid | {} |
91
+ | Timestamp| {} |
92
+ '''.format(center.value[0], center.value[1], gridcell.value, timestamp.value))
helpers/__pycache__/helpers_functional.cpython-310.pyc ADDED
Binary file (3.4 kB). View file
 
helpers/__pycache__/helpers_grid.cpython-310.pyc ADDED
Binary file (8.23 kB). View file
 
helpers/helpers_functional.py ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fsspec.parquet import open_parquet_file
2
+ import fsspec
3
+ import pyarrow.parquet as pq
4
+ from .helpers_grid import *
5
+ import pandas as pd
6
+ from io import BytesIO
7
+ import os
8
+ from PIL import Image
9
+ import datetime
10
+
11
+ # GLOBAL VARIABLES
12
+ if os.path.isfile('helpers/s2l2a_metadata.parquet'):
13
+ l2a_meta_path = 'helpers/s2l2a_metadata.parquet'
14
+ else:
15
+ DATASET_NAME = 'Major-TOM/Core-S2L2A'
16
+ l2a_meta_path = 'https://huggingface.co/datasets/{}/resolve/main/metadata.parquet'.format(DATASET_NAME)
17
+
18
+ if os.path.isfile('helpers/s2l1c_metadata.parquet'):
19
+ l1c_meta_path = 'helpers/s2l1c_metadata.parquet'
20
+ else:
21
+ DATASET_NAME = 'Major-TOM/Core-S2L1C'
22
+ l1c_meta_path = 'https://huggingface.co/datasets/{}/resolve/main/metadata.parquet'.format(DATASET_NAME)
23
+
24
+ if os.path.isfile('helpers/s1rtc_metadata.parquet'):
25
+ rtc_meta_path = 'helpers/s1rtc_metadata.parquet'
26
+ else:
27
+ DATASET_NAME = 'Major-TOM/Core-S1RTC'
28
+ rtc_meta_path = 'https://huggingface.co/datasets/{}/resolve/main/metadata.parquet'.format(DATASET_NAME)
29
+
30
+ grid = Grid(10, latitude_range=(-90,90), longitude_range=(-180,180))
31
+ l2a_df = pd.read_parquet(l2a_meta_path)
32
+ l1c_df = pd.read_parquet(l1c_meta_path)
33
+ rtc_df = pd.read_parquet(rtc_meta_path)
34
+
35
+ df_dict = {
36
+ 'S2-L2A' : l2a_df,
37
+ 'S2-L1C' : l1c_df,
38
+ 'S1-RTC' : rtc_df
39
+ }
40
+
41
+ def pretty_date(input):
42
+ template = '%Y%m%dT%H%M%S' if 'T' in input else '%Y%m%d%H%M%S'
43
+ return datetime.datetime.strptime(input, template).strftime('%H:%M:%S - %d %b %Y')
44
+
45
+ # HELPER FUNCTIONS
46
+ def gridcell2ints(grid_string):
47
+ up = int(grid_string.split('_')[0][:-1]) * (2*int(grid_string.split('_')[0][-1]=='U') - 1) # +ve if up
48
+ right = int(grid_string.split('_')[1][:-1]) * (2*int(grid_string.split('_')[1][-1]=='R') - 1) # +ve if R
49
+
50
+ return up, right
51
+
52
+ def row2image(parquet_url, parquet_row, fullrow_read=True):
53
+
54
+ if fullrow_read:
55
+ # option 1
56
+ f=fsspec.open(parquet_url)
57
+ temp_path = f.open()
58
+ else:
59
+ # option 2
60
+ temp_path = open_parquet_file(parquet_url,columns = ["thumbnail"])
61
+
62
+ with pq.ParquetFile(temp_path) as pf:
63
+ first_row_group = pf.read_row_group(parquet_row, columns=['thumbnail'])
64
+
65
+ stream = BytesIO(first_row_group['thumbnail'][0].as_py())
66
+ return Image.open(stream)
67
+
68
+ def row2s2(parquet_url, parquet_row, s2_bands = ["B04", "B03", "B02"]):
69
+ with open_parquet_file(parquet_url,columns = s2_bands) as f:
70
+ with pq.ParquetFile(f) as pf:
71
+ first_row_group = pf.read_row_group(parquet_row, columns=s2_bands)
72
+
73
+ return first_row_group
74
+
75
+ def cell2row(grid_string, meta_df, return_row = False):
76
+ row_U, col_R = gridcell2ints(grid_string)
77
+ R = meta_df.query('grid_row_u == {} & grid_col_r == {}'.format(row_U, col_R))
78
+
79
+ if not R.empty:
80
+ if return_row:
81
+ return R.parquet_url.item(), R.parquet_row.item(), R
82
+ else:
83
+ return R.parquet_url.item(), R.parquet_row.item()
84
+ else:
85
+ return None
86
+
87
+ def map_to_image(map, return_centre=False, return_gridcell=False, return_timestamp=False, source='S2-L2A'):
88
+
89
+ try:
90
+ # 1. get bounds
91
+ bbox = map.get_bbox()
92
+ center = [(bbox[3]+bbox[1])/2, (bbox[2]+bbox[0])/2]
93
+ except:
94
+ return None
95
+
96
+ # 2. translate coordinate to major-tom tile
97
+ rows, cols = grid.latlon2rowcol([center[0]], [center[1]])
98
+
99
+ # 3. translate major-tom cell to row in parquet
100
+ df = df_dict[source]
101
+ row = cell2row("{}_{}".format(rows[0],cols[0]), df, return_row = True)
102
+
103
+ if row is not None:
104
+ parquet_url, parquet_row, meta_row = row
105
+ print(meta_row)
106
+ img = row2image(parquet_url, parquet_row)
107
+ # 4. acquire image # X. update map
108
+ lat, lon = meta_row.centre_lat.item(), meta_row.centre_lon.item()
109
+
110
+ ret = [img]
111
+ if return_centre:
112
+ ret.append((lat,lon))
113
+ if return_gridcell:
114
+ ret.append(meta_row.grid_cell.item())
115
+ if return_timestamp:
116
+ ret.append(pretty_date(meta_row.timestamp.item()))
117
+
118
+ return ret
119
+ else:
120
+ return None
helpers/helpers_grid.py ADDED
@@ -0,0 +1,256 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ import math
3
+ import pandas as pd
4
+ import geopandas as gpd
5
+ from shapely.geometry import LineString, Polygon
6
+ from tqdm import tqdm
7
+
8
+
9
+
10
+ class Grid():
11
+
12
+ RADIUS_EQUATOR = 6378.137 # km
13
+
14
+ def __init__(self,dist,latitude_range=(-85,85),longitude_range=(-180,180),utm_definition='bottomleft'):
15
+ self.dist = dist
16
+ self.latitude_range = latitude_range
17
+ self.longitude_range = longitude_range
18
+ self.utm_definition = utm_definition
19
+ self.rows,self.lats = self.get_rows()
20
+ self.points, self.points_by_row = self.get_points()
21
+
22
+ def get_rows(self):
23
+
24
+ # Define set of latitudes to use, based on the grid distance
25
+ arc_pole_to_pole = math.pi * self.RADIUS_EQUATOR
26
+ num_divisions_in_hemisphere = math.ceil(arc_pole_to_pole / self.dist)
27
+
28
+ latitudes = np.linspace(-90, 90, num_divisions_in_hemisphere+1)[:-1]
29
+ latitudes = np.mod(latitudes, 180) - 90
30
+
31
+ # order should be from south to north
32
+ latitudes = np.sort(latitudes)
33
+
34
+ zeroth_row = np.searchsorted(latitudes,0)
35
+
36
+ # From 0U-NU and 1D-ND
37
+ rows = [None] * len(latitudes)
38
+ rows[zeroth_row:] = [f'{i}U' for i in range(len(latitudes)-zeroth_row)]
39
+ rows[:zeroth_row] = [f'{abs(i-zeroth_row)}D' for i in range(zeroth_row)]
40
+
41
+ # bound to range
42
+ idxs = (latitudes>=self.latitude_range[0]) * (latitudes<=self.latitude_range[1])
43
+ rows,latitudes = np.array(rows), np.array(latitudes)
44
+ rows,latitudes = rows[idxs],latitudes[idxs]
45
+
46
+ return rows,latitudes
47
+
48
+ def get_circumference_at_latitude(self,lat):
49
+
50
+ # Circumference of the cross-section of a sphere at a given latitude
51
+
52
+ radius_at_lat = self.RADIUS_EQUATOR * math.cos(lat * math.pi / 180)
53
+ circumference = 2 * math.pi * radius_at_lat
54
+
55
+ return circumference
56
+
57
+ def subdivide_circumference(self,lat,return_cols=False):
58
+ # Provide a list of longitudes that subdivide the circumference of the earth at a given latitude
59
+ # into equal parts as close as possible to dist
60
+
61
+ circumference = self.get_circumference_at_latitude(lat)
62
+ num_divisions = math.ceil(circumference / self.dist)
63
+ longitudes = np.linspace(-180,180, num_divisions+1)[:-1]
64
+ longitudes = np.mod(longitudes, 360) - 180
65
+ longitudes = np.sort(longitudes)
66
+
67
+
68
+ if return_cols:
69
+ cols = [None] * len(longitudes)
70
+ zeroth_idx = np.where(longitudes==0)[0][0]
71
+ cols[zeroth_idx:] = [f'{i}R' for i in range(len(longitudes)-zeroth_idx)]
72
+ cols[:zeroth_idx] = [f'{abs(i-zeroth_idx)}L' for i in range(zeroth_idx)]
73
+ return np.array(cols),np.array(longitudes)
74
+
75
+ return np.array(longitudes)
76
+
77
+ def get_points(self):
78
+
79
+ r_idx = 0
80
+ points_by_row = [None]*len(self.rows)
81
+ for r,lat in zip(self.rows,self.lats):
82
+ point_names,grid_row_names,grid_col_names,grid_row_idx,grid_col_idx,grid_lats,grid_lons,utm_zones,epsgs = [],[],[],[],[],[],[],[],[]
83
+ cols,lons = self.subdivide_circumference(lat,return_cols=True)
84
+
85
+ cols,lons = self.filter_longitude(cols,lons)
86
+ c_idx = 0
87
+ for c,lon in zip(cols,lons):
88
+ point_names.append(f'{r}_{c}')
89
+ grid_row_names.append(r)
90
+ grid_col_names.append(c)
91
+ grid_row_idx.append(r_idx)
92
+ grid_col_idx.append(c_idx)
93
+ grid_lats.append(lat)
94
+ grid_lons.append(lon)
95
+ if self.utm_definition == 'bottomleft':
96
+ utm_zones.append(get_utm_zone_from_latlng([lat,lon]))
97
+ elif self.utm_definition == 'center':
98
+ center_lat = lat + (1000*self.dist/2)/111_120
99
+ center_lon = lon + (1000*self.dist/2)/(111_120*math.cos(center_lat*math.pi/180))
100
+ utm_zones.append(get_utm_zone_from_latlng([center_lat,center_lon]))
101
+ else:
102
+ raise ValueError(f'Invalid utm_definition {self.utm_definition}')
103
+ epsgs.append(f'EPSG:{utm_zones[-1]}')
104
+
105
+ c_idx += 1
106
+ points_by_row[r_idx] = gpd.GeoDataFrame({
107
+ 'name':point_names,
108
+ 'row':grid_row_names,
109
+ 'col':grid_col_names,
110
+ 'row_idx':grid_row_idx,
111
+ 'col_idx':grid_col_idx,
112
+ 'utm_zone':utm_zones,
113
+ 'epsg':epsgs
114
+ },geometry=gpd.points_from_xy(grid_lons,grid_lats))
115
+ r_idx += 1
116
+ points = gpd.GeoDataFrame(pd.concat(points_by_row))
117
+ # points.reset_index(inplace=True,drop=True)
118
+ return points, points_by_row
119
+
120
+ def group_points_by_row(self):
121
+ # Make list of different gdfs for each row
122
+ points_by_row = [None]*len(self.rows)
123
+ for i,row in enumerate(self.rows):
124
+ points_by_row[i] = self.points[self.points.row==row]
125
+ return points_by_row
126
+
127
+ def filter_longitude(self,cols,lons):
128
+ idxs = (lons>=self.longitude_range[0]) * (lons<=self.longitude_range[1])
129
+ cols,lons = cols[idxs],lons[idxs]
130
+ return cols,lons
131
+
132
+ def latlon2rowcol(self,lats,lons,return_idx=False):
133
+ """
134
+ Convert latitude and longitude to row and column number from the grid
135
+ """
136
+ # Always take bottom left corner of grid cell
137
+ rows = np.searchsorted(self.lats,lats)-1
138
+
139
+ # Get the possible points of the grid cells at the given latitude
140
+ possible_points = [self.points_by_row[row] for row in rows]
141
+
142
+ # For each point, find the rightmost point that is still to the left of the given longitude
143
+ cols = [poss_points.iloc[np.searchsorted(poss_points.geometry.x,lon)-1].col for poss_points,lon in zip(possible_points,lons)]
144
+ rows = self.rows[rows]
145
+
146
+ if return_idx:
147
+ # Get the table index for self.points with each row,col pair in rows, cols
148
+ idx = [self.points[(self.points.row==row) & (self.points.col==col)].index.values[0] for row,col in zip(rows,cols)]
149
+ return rows,cols,idx
150
+ return rows,cols
151
+
152
+ def rowcol2latlon(self,rows,cols):
153
+ point_geoms = [self.points.loc[(self.points.row==row) & (self.points.col==col),'geometry'].values[0] for row,col in zip(rows,cols)]
154
+ lats = [point.y for point in point_geoms]
155
+ lons = [point.x for point in point_geoms]
156
+ return lats,lons
157
+
158
+ def get_bounded_footprint(self,point,buffer_ratio=0):
159
+ # Gets the polygon footprint of the grid cell for a given point, bounded by the other grid points' cells.
160
+ # Grid point defined as bottom-left corner of polygon. Buffer ratio is the ratio of the grid cell's width/height to buffer by.
161
+
162
+ bottom,left = point.geometry.y,point.geometry.x
163
+ row = point.row
164
+ row_idx = point.row_idx
165
+ col_idx = point.col_idx
166
+ next_row_idx = row_idx+1
167
+ next_col_idx = col_idx+1
168
+
169
+ if next_row_idx >= len(self.lats): # If at top row, use difference between top and second-to-top row for height
170
+ height = (self.lats[row_idx] - self.lats[row_idx-1])
171
+ top = self.lats[row_idx] + height
172
+ else:
173
+ top = self.lats[next_row_idx]
174
+
175
+ max_col = len(self.points_by_row[row].col_idx)-1
176
+ if next_col_idx > max_col: # If at rightmost column, use difference between rightmost and second-to-rightmost column for width
177
+ width = (self.points_by_row[row].iloc[col_idx].geometry.x - self.points_by_row[row].iloc[col_idx-1].geometry.x)
178
+ right = self.points_by_row[row].iloc[col_idx].geometry.x + width
179
+ else:
180
+ right = self.points_by_row[row].iloc[next_col_idx].geometry.x
181
+
182
+ # Buffer the polygon by the ratio of the grid cell's width/height
183
+ width = right - left
184
+ height = top - bottom
185
+
186
+ buffer_horizontal = width * buffer_ratio
187
+ buffer_vertical = height * buffer_ratio
188
+
189
+ new_left = left - buffer_horizontal
190
+ new_right = right + buffer_horizontal
191
+
192
+ new_bottom = bottom - buffer_vertical
193
+ new_top = top + buffer_vertical
194
+
195
+ bbox = Polygon([(new_left,new_bottom),(new_left,new_top),(new_right,new_top),(new_right,new_bottom)])
196
+
197
+ return bbox
198
+
199
+
200
+ def get_utm_zone_from_latlng(latlng):
201
+ """
202
+ Get the UTM ZONE from a latlng list.
203
+
204
+ Parameters
205
+ ----------
206
+ latlng : List[Union[int, float]]
207
+ The latlng list to get the UTM ZONE from.
208
+
209
+ return_epsg : bool, optional
210
+ Whether or not to return the EPSG code instead of the WKT, by default False
211
+
212
+ Returns
213
+ -------
214
+ str
215
+ The WKT or EPSG code.
216
+ """
217
+ assert isinstance(latlng, (list, np.ndarray)), "latlng must be in the form of a list."
218
+
219
+ zone = math.floor(((latlng[1] + 180) / 6) + 1)
220
+ n_or_s = "S" if latlng[0] < 0 else "N"
221
+
222
+ false_northing = "10000000" if n_or_s == "S" else "0"
223
+ central_meridian = str(zone * 6 - 183)
224
+ epsg = f"32{'7' if n_or_s == 'S' else '6'}{str(zone)}"
225
+
226
+ return epsg
227
+
228
+
229
+ if __name__ == '__main__':
230
+ import matplotlib.pyplot as plt
231
+
232
+ dist = 100
233
+ grid = Grid(dist,latitude_range=(10,70),longitude_range=(-30,60))
234
+
235
+ from pprint import pprint
236
+
237
+ test_lons = np.random.uniform(-20,50,size=(1000))
238
+ test_lats = np.random.uniform(12,68,size=(1000))
239
+
240
+ test_rows,test_cols = grid.latlon2rowcol(test_lats,test_lons)
241
+ test_lats2,test_lons2 = grid.rowcol2latlon(test_rows,test_cols)
242
+
243
+ print(test_lons[:10])
244
+ print(test_lats[:10])
245
+ print(test_rows[:10])
246
+ print(test_cols[:10])
247
+
248
+ # Make line segments from the points to their corresponding grid points
249
+ lines = []
250
+ for i in range(len(test_lats)):
251
+ lines.append([(test_lons[i],test_lats[i]),(test_lons2[i],test_lats2[i])])
252
+
253
+ lines = gpd.GeoDataFrame(geometry=gpd.GeoSeries([LineString(line) for line in lines]))
254
+
255
+ lines.to_file(f'testlines_{dist}km.geojson',driver='GeoJSON')
256
+ grid.points.to_file(f'testgrid_{dist}km.geojson',driver='GeoJSON')
helpers/s1rtc_metadata.parquet ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:013fe809ebf0e149d339e7e4b265e88787858092012eb8a422e6c3a1f546c117
3
+ size 79499536
helpers/s2l1c_metadata.parquet ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e19d451d89510923c4bc3d8acf8c45985903a73c89539e431292d6226a4b5ddc
3
+ size 171721623
helpers/s2l2a_metadata.parquet ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:9a96c80bc43cb841b8400b05e80f4b477453b51a1e6833821333e3c11831e78b
3
+ size 173048695
requirements.txt ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ leafmap
2
+ mapwidget
3
+ solara
4
+ geopandas
5
+ fsspec
6
+ pyarrow
7
+ shapely
8
+ pandas
9
+ numpy
10
+ tqdm
11
+ requests
12
+ aiohttp
13
+ pydantic<2.0