File size: 9,742 Bytes
49ee313
2cdce84
 
 
 
 
 
 
 
 
 
1398a5c
f731596
 
 
67328ef
 
2cdce84
 
 
 
 
 
 
f731596
4ed4c69
f731596
 
 
 
 
 
 
 
2cdce84
 
 
 
 
49ee313
2cdce84
 
 
 
 
f731596
2cdce84
f731596
2cdce84
 
 
 
 
f731596
2cdce84
 
f731596
 
258b6ae
 
 
1398a5c
f731596
 
 
 
2cdce84
 
 
 
 
 
 
 
 
f731596
2cdce84
 
 
440eaa6
f731596
49ee313
f731596
557f143
2cdce84
 
 
 
 
 
 
 
 
 
 
 
 
557f143
2cdce84
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f731596
 
 
2cdce84
 
 
 
 
 
 
 
f731596
 
2cdce84
 
f731596
 
 
 
 
 
6519329
2cdce84
f731596
6519329
2cdce84
6519329
2cdce84
 
 
 
 
 
 
 
 
 
f731596
 
2cdce84
 
f731596
 
2cdce84
 
 
f731596
2cdce84
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
# add_field.py
import utils
import os
import folium
import pandas as pd
import streamlit as st
import geopandas as gpd
from folium.plugins import Draw
from shapely.geometry import Polygon
from streamlit_folium import st_folium
from authentication import greeting, check_password
import geopy
from pyproj import  Transformer
from shapely.ops import transform
from geopy.geocoders import Nominatim
from shapely.ops import transform
from geopy.geocoders import Nominatim

def check_authentication():
    if not check_password():
        st.stop()



# Function to get coordinates from a location name
def get_location_coordinates(location_name,geolocator):
    try:
        location = geolocator.geocode(location_name)
        if location:
            return location.latitude, location.longitude
        else:
            return None, None
    except:
        return None, None

def display_existing_fields(current_user):
    with st.expander("Existing Fields", expanded=False):
        if os.path.exists(f"fields_{current_user}.parquet"):
            gdf = gpd.read_parquet(f"fields_{current_user}.parquet")
            st.table(gdf.name.tolist())
            mm = gdf.explore()
            st_folium(mm)
        else:
            st.info("No Fields Added Yet!")

def add_existing_fields_to_map(field_map, current_user):
    if os.path.exists(f"fields_{current_user}.parquet"):
        fg = folium.FeatureGroup(name="Existing Fields", control=True).add_to(field_map)
        gdf = gpd.read_parquet(f"fields_{current_user}.parquet")
        for i, row in gdf.iterrows():
            edges = row['geometry'].exterior.coords.xy
            edges = [[i[1], i[0]] for i in zip(*edges)]
            folium.Polygon(edges, color='blue', fill=True, fill_color='blue', fill_opacity=0.6).add_to(fg)
    return field_map

def get_center_of_existing_fields(current_user):
    location_name = st.text_input('Enter a location to search:')
    if location_name:
        geolocator = Nominatim(user_agent=current_user)
        geopy.geocoders.options.default_user_agent = current_user

        lat, lon = get_location_coordinates(location_name,geolocator)
        if lat is not None and lon is not None:
            return [lat, lon]
        else:
            st.error('Location not found. Please try again.')
    if os.path.exists(f"fields_{current_user}.parquet"):
        gdf = gpd.read_parquet(f"fields_{current_user}.parquet")
        edges = gdf['geometry'][0].exterior.coords.xy
        edges = [[i[1], i[0]] for i in zip(*edges)]
        edges_center = [sum([i[0] for i in edges]) / len(edges), sum([i[1] for i in edges]) / len(edges)]

        return edges_center
    return [15.572363674301132, 32.69167103104079]

def display_map_and_drawing_controls(field_map, center_start):
    zoom_start = 13
    if st.session_state['active_drawing'] is None:
        st.info("IMPORTANT: Click on the drawing to confirm the drawn field", icon="🚨")
        sat_basemap = utils.basemaps['Google Satellite Hybrid']  # Change this line to use 'Google Satellite Hybrid'
        sat_basemap.add_to(field_map)
        folium.plugins.Geocoder().add_to(field_map)
        folium.LayerControl().add_to(field_map)
        output = st_folium(field_map, center=center_start, zoom=zoom_start, key="new", width=900)
        active_drawing = output['last_active_drawing']
        st.session_state['active_drawing'] = active_drawing
        return False
    else:
        st.info("Drawing Captured! Click on the button below to Clear Drawing and Draw Again")
        active_drawing = st.session_state['active_drawing']
        new_map = folium.Map(location=center_start, zoom_start=8)
        edges = [[i[1], i[0]] for i in active_drawing['geometry']['coordinates'][0]]
        edges_center = [sum([i[0] for i in edges]) / len(edges), sum([i[1] for i in edges]) / len(edges)]
        folium.Polygon(edges, color='green', fill=True, fill_color='green', fill_opacity=0.6, name="New Field").add_to(new_map)
        sat_basemap = utils.basemaps['Google Satellite']
        sat_basemap.add_to(new_map)
        folium.LayerControl().add_to(new_map)
        st_folium(new_map, center=edges_center, zoom=zoom_start, key="drawn", width=900)
        return True

def handle_user_actions(active_drawing, current_user, intersects, within_area):
    draw_again_col, add_field_info_col = st.columns([1, 1])
    with draw_again_col:
        draw_again = st.button("Draw Again", key="draw_again", help="Click to Clear Drawing and Draw Again",
                               type="primary", use_container_width=True, disabled=st.session_state['active_drawing'] is None)
        if draw_again:
            st.session_state['active_drawing'] = None
            st.rerun()
    with add_field_info_col:
        if st.session_state['active_drawing'] is None:
            st.info("Drawing not captured yet!")
        else:
            field_name = st.text_input("Field Name*", help="Enter a distinct name for the field", key="field_name")
            if field_name == "":
                st.warning("Field Name cannot be empty!")
            if os.path.exists(f"fields_{current_user}.parquet"):
                gdf = gpd.read_parquet(f"fields_{current_user}.parquet")
                if field_name in gdf['name'].tolist():
                    st.warning("Field Name already exists. Please enter a different name!")
            submit = st.button("Submit", key="submit", help="Click to Submit Field Information", type="primary",
                               use_container_width=True,disabled=(st.session_state['active_drawing'] is None or field_name == "") or intersects or not within_area)
            if submit:
                save_field_information(active_drawing, field_name, current_user)
                st.success("Field Information Submitted Successfully!")
                st.session_state['active_drawing'] = None
                st.rerun()

def save_field_information(active_drawing, field_name, current_user):
    edges = [[i[0], i[1]] for i in active_drawing['geometry']['coordinates'][0]]
    geom = Polygon(edges)
    field_dict = {
        "name": field_name,
        "geometry": geom
    }
    gdf = gpd.GeoDataFrame([field_dict], geometry='geometry')
    gdf.crs = "EPSG:4326"
    if os.path.exists(f"fields_{current_user}.parquet"):
        old_gdf = gpd.read_parquet(f"fields_{current_user}.parquet")
        gdf = gpd.GeoDataFrame(pd.concat([old_gdf, gdf], ignore_index=True), crs="EPSG:4326")
    gdf.to_parquet(f"fields_{current_user}.parquet")

def initialize_active_drawing_state():
    if 'active_drawing' not in st.session_state:
        st.session_state['active_drawing'] = None
    if 'current_user' not in st.session_state:
        st.session_state['current_user'] = None
    


def check_intersection_with_existing_fields(active_drawing, current_user):
    if active_drawing is None:
        return False
    if os.path.exists(f"fields_{current_user}.parquet"):
        gdf = gpd.read_parquet(f"fields_{current_user}.parquet")
        edges = [[i[0], i[1]] for i in active_drawing['geometry']['coordinates'][0]]
        geom = Polygon(edges)
        geom = gpd.GeoSeries([geom]*len(gdf), crs="EPSG:4326")
        geom1 = geom.to_crs(gdf.crs)
        geom2 = gdf.geometry.to_crs(gdf.crs)
        if geom1.overlaps(geom2).any():
            st.warning("Field intersects with existing fields. Please draw again!")
            with st.expander("Intersecting Fields", expanded=False):
                field_map = geom1.explore(name= "New Field", color="red")
                field_map = gdf.explore(m=field_map, name="Existing Fields", color="blue")
                st_folium(field_map)
            return True
    return False



def check_polygon_area_within_range(active_drawing, min_area_km2=1, max_area_km2=10):
    if active_drawing is None:
        return
    transformer = Transformer.from_crs("EPSG:4326", "EPSG:6933", always_xy=True)

    edges = [[i[0], i[1]] for i in active_drawing['geometry']['coordinates'][0]]
    geom = Polygon(edges)
    transformed_geom = transform(transformer.transform, geom)


    area_km2 = transformed_geom.area / 10**6

    if area_km2 < min_area_km2:
        st.warning(f"Field area {area_km2 :.2f} is less than {min_area_km2} km². Please draw again!")
        return False
    if area_km2 > max_area_km2:
        st.warning(f"Field area {area_km2 :.2f} is more than {max_area_km2} km². Please draw again!")
        return False
    st.success(f"Field area is {area_km2 :.2f} km², now give it a unique name {st.session_state['current_user']}!")
    return True


def add_drawing():
    initialize_active_drawing_state()
    current_user = greeting("Drag and Zoom and draw your fields on the map, make sure to name them uniquely")
    current_user = st.session_state['current_user']
    display_existing_fields(current_user)
    center_start = get_center_of_existing_fields(current_user)
    zoom_start = 13
    field_map = folium.Map(location=center_start, zoom_start=zoom_start)


    draw_options = {'polyline': False, 'polygon': True, 'rectangle': True, 'circle': False, 'marker': False, 'circlemarker': False}
    Draw(export=True, draw_options=draw_options).add_to(field_map)
    field_map = add_existing_fields_to_map(field_map, current_user)



    captured = display_map_and_drawing_controls(field_map, center_start)
    if captured:
        intersects = check_intersection_with_existing_fields(st.session_state['active_drawing'], current_user)
        within_area = check_polygon_area_within_range(st.session_state['active_drawing'])
        handle_user_actions(st.session_state['active_drawing'], current_user, intersects, within_area)
    

if __name__ == '__main__':
    check_authentication()

    add_drawing()