diff --git a/.gitignore b/.gitignore index 78fc94d..7d3e0f0 100644 --- a/.gitignore +++ b/.gitignore @@ -44,6 +44,7 @@ terraform.tfstate* serverless/*/lambda.zip serverless/*/src/* !serverless/*/src/alpr_counts.py +!serverless/*/src/alpr_clusters.py !serverless/*/src/requirements.txt # TODO: need a better way to handle python packages diff --git a/clustering/alpr_clusters.json b/clustering/alpr_clusters.json deleted file mode 100644 index 92726eb..0000000 --- a/clustering/alpr_clusters.json +++ /dev/null @@ -1,974 +0,0 @@ -{ - "clusters": [ - { - "lat": 52.732404423221325, - "lon": -1.5764258991106712, - "id": 212781 - }, - { - "lat": 47.56745800097088, - "lon": 18.019815189320383, - "id": 26733510 - }, - { - "lat": 51.08509535957224, - "lon": 4.623003680320856, - "id": 26748849 - }, - { - "lat": 52.189532893750005, - "lon": 10.392907087500001, - "id": 30144501 - }, - { - "lat": 37.709913511656445, - "lon": -122.30694874969323, - "id": 53092228 - }, - { - "lat": 42.43266967878788, - "lon": -71.21201071111106, - "id": 61400195 - }, - { - "lat": 42.38934587522125, - "lon": -83.23396026017703, - "id": 62506088 - }, - { - "lat": 38.94347505, - "lon": -122.61952615, - "id": 85867866 - }, - { - "lat": 40.66668550571429, - "lon": -74.08348858857143, - "id": 104633967 - }, - { - "lat": -34.59345612395832, - "lon": -58.492578049479164, - "id": 241975905 - }, - { - "lat": 56.899951924999996, - "lon": 53.16952525, - "id": 255856115 - }, - { - "lat": 12.144520700000001, - "lon": -68.27292433333334, - "id": 278303396 - }, - { - "lat": 45.4610852375, - "lon": 12.0556567, - "id": 290025426 - }, - { - "lat": 40.78654777741936, - "lon": 72.34269644193549, - "id": 304747721 - }, - { - "lat": 42.43366232957746, - "lon": 19.25682327464788, - "id": 304836826 - }, - { - "lat": 40.692390192, - "lon": 16.682913912, - "id": 388279506 - }, - { - "lat": 45.14034804146342, - "lon": 10.177569271138205, - "id": 402291931 - }, - { - "lat": 42.545926, - "lon": -6.59531462857143, - "id": 486799226 - }, - { - "lat": 38.23947571666667, - "lon": 27.168448366666667, - "id": 506196190 - }, - { - "lat": 53.1019164, - "lon": 8.8266571, - "id": 550390297 - }, - { - "lat": 52.45130200428572, - "lon": 13.735323098571431, - "id": 559557998 - }, - { - "lat": 55.614296776712365, - "lon": 10.688850217808223, - "id": 583850576 - }, - { - "lat": -32.5698644, - "lon": -65.2043375, - "id": 598031064 - }, - { - "lat": 49.18208440909091, - "lon": 9.374031084090909, - "id": 653333122 - }, - { - "lat": 40.445664855555556, - "lon": -3.6551794444444443, - "id": 992003841 - }, - { - "lat": 46.5518581, - "lon": 0.3211684, - "id": 1481601113 - }, - { - "lat": -37.1105355, - "lon": 146.3996596, - "id": 1775755949 - }, - { - "lat": 41.88948087106107, - "lon": -87.69727232765273, - "id": 1931296905 - }, - { - "lat": 42.78282511249999, - "lon": -1.8327840625, - "id": 2161463239 - }, - { - "lat": -33.8872546, - "lon": 151.19508455, - "id": 2471675882 - }, - { - "lat": 47.82578458421053, - "lon": 11.277529773684211, - "id": 2540954475 - }, - { - "lat": -32.92934838461538, - "lon": -60.78882008461539, - "id": 2575002589 - }, - { - "lat": 47.5551079, - "lon": 7.7896264, - "id": 2712961963 - }, - { - "lat": 54.3215178, - "lon": 22.2975114, - "id": 2791180330 - }, - { - "lat": 49.678914500000005, - "lon": 25.124377799999998, - "id": 2990489748 - }, - { - "lat": 53.41295146666666, - "lon": 58.99338063333334, - "id": 3049183315 - }, - { - "lat": 54.19036753333333, - "lon": 15.795299, - "id": 3150691290 - }, - { - "lat": 55.7899815, - "lon": 38.4308137, - "id": 3151851060 - }, - { - "lat": 35.7246815, - "lon": -83.5101922, - "id": 3358378465 - }, - { - "lat": 42.0126763, - "lon": -4.5150433, - "id": 3379654702 - }, - { - "lat": 35.7279372375, - "lon": 139.98782023750002, - "id": 3818433757 - }, - { - "lat": 24.48206279375, - "lon": 54.54188122500001, - "id": 4003632176 - }, - { - "lat": 63.7669597, - "lon": 11.4547029, - "id": 4040253751 - }, - { - "lat": 40.5069511, - "lon": 68.7573655, - "id": 4167811245 - }, - { - "lat": 14.255630155555556, - "lon": 121.04478345000001, - "id": 4279277342 - }, - { - "lat": 42.458649771929814, - "lon": 59.61558534385965, - "id": 4375850761 - }, - { - "lat": 41.294033222222225, - "lon": 69.28546985555556, - "id": 4792739369 - }, - { - "lat": 39.6690496, - "lon": -77.7176339, - "id": 4795640515 - }, - { - "lat": 41.893131344444456, - "lon": 12.494813403703704, - "id": 4807332468 - }, - { - "lat": 44.6982733, - "lon": 17.1944327, - "id": 4902551036 - }, - { - "lat": 44.53316085, - "lon": 33.552830549999996, - "id": 4984788122 - }, - { - "lat": -31.3829959, - "lon": -52.6918941, - "id": 5043054317 - }, - { - "lat": 32.6570023, - "lon": 51.6663904, - "id": 5058232608 - }, - { - "lat": 44.5544037, - "lon": 38.1119744, - "id": 5139068722 - }, - { - "lat": 56.833408933333324, - "lon": 24.133528466666665, - "id": 5208722723 - }, - { - "lat": 52.498303250000006, - "lon": 103.936603475, - "id": 5316459794 - }, - { - "lat": 43.66505204, - "lon": 4.65540847, - "id": 5551987715 - }, - { - "lat": 43.18043762857143, - "lon": 2.745652242857143, - "id": 5671457593 - }, - { - "lat": 60.21154923333333, - "lon": 24.852057883333334, - "id": 5739209507 - }, - { - "lat": -27.57210675, - "lon": 153.10341286666667, - "id": 5746205033 - }, - { - "lat": 45.46046806666667, - "lon": 130.97369166666667, - "id": 5816874438 - }, - { - "lat": -34.7808275, - "lon": -65.2600588, - "id": 6016769883 - }, - { - "lat": -34.8867194, - "lon": -56.1385995, - "id": 6018626055 - }, - { - "lat": 48.7798547, - "lon": 5.1663791, - "id": 6153884138 - }, - { - "lat": 35.52809005, - "lon": -78.31248415, - "id": 6174245977 - }, - { - "lat": 60.13572106666666, - "lon": 11.032399925, - "id": 6188791947 - }, - { - "lat": 53.29233903333334, - "lon": -6.4701217, - "id": 6262199245 - }, - { - "lat": 45.028034357142865, - "lon": 7.674925185714286, - "id": 6281644435 - }, - { - "lat": 43.64369324285714, - "lon": 1.3643660857142856, - "id": 6361216535 - }, - { - "lat": 39.05453263, - "lon": -0.10385178999999997, - "id": 6514284851 - }, - { - "lat": 12.8699376, - "lon": 77.6533944, - "id": 6539731572 - }, - { - "lat": 59.99463045, - "lon": 30.35622135, - "id": 6713062800 - }, - { - "lat": 35.80318377142857, - "lon": 51.25868582857144, - "id": 6737053121 - }, - { - "lat": 43.69937303333334, - "lon": 7.2819903, - "id": 6745958839 - }, - { - "lat": 30.3164844, - "lon": 111.4785194, - "id": 6781732096 - }, - { - "lat": 46.8367024, - "lon": 29.624909, - "id": 6892206595 - }, - { - "lat": 46.14686631428571, - "lon": 6.172505428571428, - "id": 6928121637 - }, - { - "lat": -15.7748474, - "lon": -47.8901917, - "id": 6933101521 - }, - { - "lat": 43.369522, - "lon": -8.3996189, - "id": 6953949352 - }, - { - "lat": 39.27053958333333, - "lon": -84.48775338333333, - "id": 7105924326 - }, - { - "lat": 43.7658291, - "lon": 59.0272368, - "id": 7127665150 - }, - { - "lat": 51.14479765, - "lon": 34.3135918, - "id": 7176878182 - }, - { - "lat": 34.18648089666667, - "lon": -118.31329852499998, - "id": 7428089366 - }, - { - "lat": 48.978202499999995, - "lon": 14.474031100000001, - "id": 7526182056 - }, - { - "lat": 45.05926715, - "lon": 38.98956765, - "id": 7653161419 - }, - { - "lat": -33.44328448450705, - "lon": -70.6181848830986, - "id": 7705911244 - }, - { - "lat": -21.6908403, - "lon": -45.2390777, - "id": 7716567940 - }, - { - "lat": 46.66588165, - "lon": 32.68906265, - "id": 7757063873 - }, - { - "lat": 44.7902348, - "lon": 20.4538768, - "id": 7777750239 - }, - { - "lat": -38.94850192222222, - "lon": -68.0492845, - "id": 7795756969 - }, - { - "lat": 48.34348296666667, - "lon": 29.866569433333336, - "id": 7815050702 - }, - { - "lat": 49.198327500000005, - "lon": 16.6036377, - "id": 7956380414 - }, - { - "lat": -26.6066294, - "lon": -53.058778399999994, - "id": 7968782457 - }, - { - "lat": 50.74850008, - "lon": 25.31996091, - "id": 8247564853 - }, - { - "lat": 38.09874255, - "lon": 15.140991283333335, - "id": 8294434964 - }, - { - "lat": 10.9597213, - "lon": 106.6884558, - "id": 8359734753 - }, - { - "lat": 39.9521153, - "lon": 116.3294079, - "id": 8725102416 - }, - { - "lat": 55.06267, - "lon": 83.2075264, - "id": 8902753938 - }, - { - "lat": 17.1068462, - "lon": 42.6550979, - "id": 8928447784 - }, - { - "lat": 48.85871435714286, - "lon": 2.3348782357142857, - "id": 8929093241 - }, - { - "lat": 41.354143101119405, - "lon": -81.35892472201489, - "id": 8948981708 - }, - { - "lat": -30.0454996, - "lon": -51.0828317, - "id": 8957729203 - }, - { - "lat": 35.649095, - "lon": -105.96741025, - "id": 8982689960 - }, - { - "lat": 36.58238344, - "lon": -121.92447173000001, - "id": 9097102379 - }, - { - "lat": 42.8634008, - "lon": 0.7488745, - "id": 9103892625 - }, - { - "lat": 55.95849660000001, - "lon": -3.2760497, - "id": 9148459635 - }, - { - "lat": 32.6202096, - "lon": -85.404587, - "id": 9180837873 - }, - { - "lat": 49.7838112, - "lon": 13.3845051, - "id": 9315915453 - }, - { - "lat": 35.759663775, - "lon": 115.0691568, - "id": 9348368733 - }, - { - "lat": 36.96401410000001, - "lon": -76.63336733333334, - "id": 9366175968 - }, - { - "lat": 45.0490455, - "lon": 41.9839839, - "id": 9370917038 - }, - { - "lat": 41.0718644, - "lon": 14.336202, - "id": 9397537407 - }, - { - "lat": 59.0514187, - "lon": 10.0212121, - "id": 9445528897 - }, - { - "lat": 38.7411816, - "lon": -90.3648156, - "id": 9449104386 - }, - { - "lat": 33.94000148965517, - "lon": -84.49053553793105, - "id": 9559274111 - }, - { - "lat": -27.4200355, - "lon": -55.8794206, - "id": 9643363333 - }, - { - "lat": 30.20073885, - "lon": 115.0153711, - "id": 9668726265 - }, - { - "lat": 47.61664426666667, - "lon": -122.17338390000002, - "id": 9681269303 - }, - { - "lat": 35.4986225875, - "lon": 129.2936829, - "id": 9768814056 - }, - { - "lat": 46.06916597777778, - "lon": 13.889542333333331, - "id": 9772521365 - }, - { - "lat": -37.6716382, - "lon": 176.220261, - "id": 9786923901 - }, - { - "lat": 36.39862252272728, - "lon": 34.00519811818181, - "id": 10080446380 - }, - { - "lat": 43.3201357, - "lon": -2.8626823, - "id": 10095004238 - }, - { - "lat": 44.6726205, - "lon": 26.2035561, - "id": 10115182351 - }, - { - "lat": 46.8188776, - "lon": -95.8531688, - "id": 10118113557 - }, - { - "lat": 14.4585228, - "lon": -87.63972486, - "id": 10164408209 - }, - { - "lat": -17.39562645, - "lon": -66.15368415, - "id": 10213726234 - }, - { - "lat": 43.544113625, - "lon": 76.98817112500001, - "id": 10217000724 - }, - { - "lat": 37.3619472, - "lon": 126.7378353, - "id": 10289044027 - }, - { - "lat": 54.45324969500001, - "lon": -5.9791662049999985, - "id": 10291636996 - }, - { - "lat": 47.09578485, - "lon": 13.6134917, - "id": 10313224844 - }, - { - "lat": 38.98316559743589, - "lon": -94.97414200512819, - "id": 10416000563 - }, - { - "lat": 39.89354184625849, - "lon": -89.26944156122447, - "id": 10542792518 - }, - { - "lat": 47.009861, - "lon": -1.8843209666666667, - "id": 10693141819 - }, - { - "lat": 39.97560084264707, - "lon": -82.93688099999997, - "id": 10800510096 - }, - { - "lat": 42.67809416874999, - "lon": -79.06118295625, - "id": 10828130058 - }, - { - "lat": 36.04796596, - "lon": -95.997575665, - "id": 10911752786 - }, - { - "lat": 26.12919874, - "lon": -80.34144024, - "id": 10937129488 - }, - { - "lat": 41.505362009090916, - "lon": 1.0239366272727273, - "id": 10939031176 - }, - { - "lat": 42.964835883333336, - "lon": -77.01975345416666, - "id": 10945232508 - }, - { - "lat": 35.48426370970873, - "lon": -97.52778463689327, - "id": 11001547575 - }, - { - "lat": 35.2148465, - "lon": -100.74404185, - "id": 11019052023 - }, - { - "lat": 35.23040005, - "lon": -102.85522995, - "id": 11019052025 - }, - { - "lat": 31.542903014634152, - "lon": -97.3923150512195, - "id": 11019052027 - }, - { - "lat": 5.2272983, - "lon": 100.4254328, - "id": 11054240119 - }, - { - "lat": 39.95067505, - "lon": -86.1269044, - "id": 11128741981 - }, - { - "lat": 30.188785101886793, - "lon": -85.66608733207548, - "id": 11153415133 - }, - { - "lat": -36.0435284, - "lon": 140.3033339, - "id": 11164917239 - }, - { - "lat": 56.7973144, - "lon": 61.3192467, - "id": 11174880992 - }, - { - "lat": 24.997346200000003, - "lon": 51.548919049999995, - "id": 11193090974 - }, - { - "lat": 37.047959725, - "lon": 35.2987446, - "id": 11212439046 - }, - { - "lat": 11.33355195, - "lon": 125.6152166, - "id": 11226725209 - }, - { - "lat": -28.425582, - "lon": -65.72378605, - "id": 11276569104 - }, - { - "lat": 53.8964187, - "lon": 27.6426795, - "id": 11376019979 - }, - { - "lat": 29.686285447058815, - "lon": -95.49650955686272, - "id": 11390366547 - }, - { - "lat": 44.8383932, - "lon": -0.5691424, - "id": 11427748333 - }, - { - "lat": 42.855520049999996, - "lon": 13.82151575, - "id": 11573031141 - }, - { - "lat": 22.2698036, - "lon": 113.9346059, - "id": 11575963642 - }, - { - "lat": 36.91654560909091, - "lon": -2.229015381818182, - "id": 11578259953 - }, - { - "lat": 28.62320878235294, - "lon": 77.26864867058823, - "id": 11775354712 - }, - { - "lat": 32.3765604, - "lon": -104.2289746, - "id": 11805581107 - }, - { - "lat": 35.212911899999995, - "lon": -94.26012285, - "id": 11805581108 - }, - { - "lat": 37.396571172222224, - "lon": -79.19256750555556, - "id": 11873051110 - }, - { - "lat": 20.7819613, - "lon": 105.9021437, - "id": 11890773916 - }, - { - "lat": 35.32606256666667, - "lon": -84.3774859, - "id": 11914271298 - }, - { - "lat": -43.73740755, - "lon": 172.0457342, - "id": 11954825997 - }, - { - "lat": -7.4278682, - "lon": 146.6685026, - "id": 11956049019 - }, - { - "lat": 34.1822572, - "lon": -97.15479933333334, - "id": 12007388723 - }, - { - "lat": 45.7485163, - "lon": 4.8583428, - "id": 12017283553 - }, - { - "lat": 39.0766616, - "lon": 17.130523949999997, - "id": 12025285545 - }, - { - "lat": 67.9351106, - "lon": 13.0887991, - "id": 12037972445 - }, - { - "lat": -7.9050373, - "lon": 112.5693157, - "id": 12048260217 - }, - { - "lat": 50.223537, - "lon": 21.80047785, - "id": 12066868688 - }, - { - "lat": 41.3665805, - "lon": 60.5992706, - "id": 12079512103 - }, - { - "lat": 55.0995108, - "lon": 14.692537, - "id": 12083930964 - }, - { - "lat": 51.14395195, - "lon": 14.9802798, - "id": 12131061963 - }, - { - "lat": 42.44321505, - "lon": -123.3743058, - "id": 12177063007 - }, - { - "lat": 27.71026544, - "lon": -97.36848358, - "id": 12184882598 - }, - { - "lat": 34.68302499146343, - "lon": -86.6719257682927, - "id": 12187369976 - }, - { - "lat": -21.3832554, - "lon": -51.713266499999996, - "id": 12197869463 - }, - { - "lat": 52.44952085, - "lon": 16.702914075000002, - "id": 12207832066 - }, - { - "lat": 51.089264, - "lon": 16.9948541, - "id": 12213717696 - }, - { - "lat": 37.5721757882353, - "lon": -77.40523577647059, - "id": 12288735372 - }, - { - "lat": 25.9507272, - "lon": -97.5091525, - "id": 12291389477 - }, - { - "lat": 39.82849021935483, - "lon": -105.08539391290321, - "id": 12294183847 - }, - { - "lat": 42.82092225, - "lon": -85.907112175, - "id": 12294771482 - }, - { - "lat": 39.9061195, - "lon": 32.7868066, - "id": 12300082921 - }, - { - "lat": 38.0333561, - "lon": -84.5243437, - "id": 12331056577 - }, - { - "lat": 27.372223575, - "lon": -82.549264025, - "id": 12331080088 - }, - { - "lat": 35.126027820000004, - "lon": -89.94004336, - "id": 12331283276 - }, - { - "lat": 38.0016849, - "lon": -87.5823694, - "id": 12331572923 - }, - { - "lat": 41.2105516, - "lon": -111.96655874999999, - "id": 12332700153 - }, - { - "lat": 44.96608164375, - "lon": -92.72644428750002, - "id": 12333489524 - }, - { - "lat": 43.3229691, - "lon": -87.92466653333334, - "id": 12333501372 - } - ] -} \ No newline at end of file diff --git a/clustering/cluster.py b/clustering/cluster.py deleted file mode 100644 index 36aaddc..0000000 --- a/clustering/cluster.py +++ /dev/null @@ -1,62 +0,0 @@ -import requests -import json -from sklearn.cluster import DBSCAN -from geopy.distance import great_circle -from geopy.point import Point -import numpy as np - -# Set up the Overpass API query -# TODO: remove the bbox -query = """ -[out:json]; -node["man_made"="surveillance"]["surveillance:type"="ALPR"]; -out body; -""" - -# Request data from Overpass API -print("Requesting data from Overpass API...") -url = "http://overpass-api.de/api/interpreter" -response = requests.get(url, params={'data': query}, headers={'User-Agent': 'DeFlock/1.0'}) -data = response.json() -print("Data received. Parsing nodes...") - -# Parse nodes and extract lat/lon for clustering -coordinates = [] -node_ids = [] -for element in data['elements']: - if element['type'] == 'node': - coordinates.append([element['lat'], element['lon']]) - node_ids.append(element['id']) - -# Convert coordinates to NumPy array for DBSCAN -coordinates = np.array(coordinates) - -# Define the clustering radius (10 miles in meters) -radius_miles = 50 -radius_km = radius_miles * 1.60934 # 1 mile = 1.60934 km -radius_in_radians = radius_km / 6371.0 # Earth's radius in km - -# Perform DBSCAN clustering -db = DBSCAN(eps=radius_in_radians, min_samples=1, algorithm='ball_tree', metric='haversine').fit(np.radians(coordinates)) -labels = db.labels_ - -# Prepare clusters and calculate centroids -clusters = {} -for label in set(labels): - cluster_points = coordinates[labels == label] - centroid = np.mean(cluster_points, axis=0) - first_node_id = node_ids[labels.tolist().index(label)] - - # Store in clusters dict with centroid and first node ID - clusters[label] = { - "lat": centroid[0], - "lon": centroid[1], - "id": first_node_id - } - -# Save clusters to JSON -output = {"clusters": list(clusters.values())} -with open("alpr_clusters.json", "w") as outfile: - json.dump(output, outfile, indent=2) - -print("Clustering complete. Results saved to alpr_clusters.json.") diff --git a/serverless/alpr_clusters/deploy.sh b/serverless/alpr_clusters/deploy.sh new file mode 100755 index 0000000..557c93b --- /dev/null +++ b/serverless/alpr_clusters/deploy.sh @@ -0,0 +1,31 @@ +#!/bin/bash + +ECR_REPO_URL=912821578123.dkr.ecr.us-east-1.amazonaws.com/alpr_clusters-lambda + +set -e + +# check if AWS role is assumed +if ! aws sts get-caller-identity &> /dev/null; then + echo "Error: AWS role is not assumed. Please assume the necessary role and try again." + exit 1 +fi + +cd src + +# build Docker image +docker build -t alpr_clusters . + +# tag docker image with ECR repo +docker tag alpr_clusters:latest $ECR_REPO_URL:latest + +# login to ECR +aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin $ECR_REPO_URL + +# push Docker image to ECR +docker push $ECR_REPO_URL:latest + +# update lambda function +export AWS_PAGER="" +aws lambda update-function-code --function-name alpr_clusters --image-uri $ECR_REPO_URL:latest + +echo "Deployed!" diff --git a/serverless/alpr_clusters/src/alpr_clusters.py b/serverless/alpr_clusters/src/alpr_clusters.py new file mode 100644 index 0000000..f00b092 --- /dev/null +++ b/serverless/alpr_clusters/src/alpr_clusters.py @@ -0,0 +1,78 @@ +import boto3 +import requests +import json +from sklearn.cluster import DBSCAN +import numpy as np + +def get_clusters(): + # Set up the Overpass API query + query = """ + [out:json]; + node["man_made"="surveillance"]["surveillance:type"="ALPR"]; + out body; + """ + + # Request data from Overpass API + print("Requesting data from Overpass API.") + url = "http://overpass-api.de/api/interpreter" + response = requests.get(url, params={'data': query}, headers={'User-Agent': 'DeFlock/1.0'}) + data = response.json() + print("Data received. Parsing nodes...") + + # Parse nodes and extract lat/lon for clustering + coordinates = [] + node_ids = [] + for element in data['elements']: + if element['type'] == 'node': + coordinates.append([element['lat'], element['lon']]) + node_ids.append(element['id']) + + # Convert coordinates to NumPy array for DBSCAN + coordinates = np.array(coordinates) + + # Define the clustering radius (10 miles in meters) + radius_miles = 50 + radius_km = radius_miles * 1.60934 # 1 mile = 1.60934 km + radius_in_radians = radius_km / 6371.0 # Earth's radius in km + + # Perform DBSCAN clustering + db = DBSCAN(eps=radius_in_radians, min_samples=1, algorithm='ball_tree', metric='haversine').fit(np.radians(coordinates)) + labels = db.labels_ + + # Prepare clusters and calculate centroids + clusters = {} + for label in set(labels): + cluster_points = coordinates[labels == label] + centroid = np.mean(cluster_points, axis=0) + first_node_id = node_ids[labels.tolist().index(label)] + + # Store in clusters dict with centroid and first node ID + clusters[label] = { + "lat": centroid[0], + "lon": centroid[1], + "id": first_node_id + } + + output = {"clusters": list(clusters.values())} + print("Clustering complete.") + return output + + +def lambda_handler(event, context): + alpr_clusters = get_clusters() + + s3 = boto3.client('s3') + bucket = 'deflock-clusters' + key = 'alpr_clusters.json' + + s3.put_object( + Bucket=bucket, + Key=key, + Body=json.dumps(alpr_clusters), + ContentType='application/json' + ) + + return { + 'statusCode': 200, + 'body': 'Successfully fetched ALPR counts.', + } diff --git a/serverless/alpr_clusters/src/requirements.txt b/serverless/alpr_clusters/src/requirements.txt new file mode 100644 index 0000000..73c3ba1 --- /dev/null +++ b/serverless/alpr_clusters/src/requirements.txt @@ -0,0 +1,4 @@ +boto3 +requests +scikit-learn +numpy diff --git a/serverless/alpr-counts/src/alpr_counts.py b/serverless/alpr_counts/src/alpr_counts.py similarity index 100% rename from serverless/alpr-counts/src/alpr_counts.py rename to serverless/alpr_counts/src/alpr_counts.py diff --git a/serverless/alpr-counts/src/requirements.txt b/serverless/alpr_counts/src/requirements.txt similarity index 100% rename from serverless/alpr-counts/src/requirements.txt rename to serverless/alpr_counts/src/requirements.txt diff --git a/terraform/main.tf b/terraform/main.tf new file mode 100644 index 0000000..e9b2896 --- /dev/null +++ b/terraform/main.tf @@ -0,0 +1,17 @@ +provider "aws" { + region = "us-east-1" +} + +module "alpr_counts" { + module_name = "alpr_counts" + source = "./modules/alpr_counts" + deflock_stats_bucket = var.deflock_stats_bucket + rate = "rate(60 minutes)" +} + +module "alpr_clusters" { + module_name = "alpr_clusters" + source = "./modules/alpr_clusters" + deflock_stats_bucket = var.deflock_stats_bucket + rate = "rate(1 day)" +} diff --git a/terraform/modules/alpr_clusters/main.tf b/terraform/modules/alpr_clusters/main.tf new file mode 100644 index 0000000..2488460 --- /dev/null +++ b/terraform/modules/alpr_clusters/main.tf @@ -0,0 +1,72 @@ +resource "aws_iam_role" "lambda_role" { + name = "${var.module_name}_lambda_s3_write_role" + + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Action = "sts:AssumeRole" + Effect = "Allow" + Principal = { + Service = "lambda.amazonaws.com" + } + } + ] + }) +} + +resource "aws_iam_policy" "lambda_s3_write_policy" { + name = "${var.module_name}_lambda_s3_write_policy" + description = "Policy for Lambda to write to S3 bucket ${var.deflock_stats_bucket}" + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Action = [ + "s3:PutObject", + "s3:PutObjectAcl" + ] + Effect = "Allow" + Resource = "arn:aws:s3:::${var.deflock_stats_bucket}/${var.output_filename}" + } + ] + }) +} + +resource "aws_iam_role_policy_attachment" "lambda_s3_write_attachment" { + role = aws_iam_role.lambda_role.name + policy_arn = aws_iam_policy.lambda_s3_write_policy.arn +} + +resource "aws_lambda_function" "overpass_lambda" { + function_name = var.module_name + role = aws_iam_role.lambda_role.arn + package_type = "Image" + image_uri = "${aws_ecr_repository.lambda_repository.repository_url}:latest" + timeout = 90 +} + +resource "aws_cloudwatch_event_rule" "lambda_rule" { + name = "${var.module_name}_rule" + description = "Rule to trigger ${var.module_name} lambda" + schedule_expression = var.rate +} + +resource "aws_cloudwatch_event_target" "lambda_target" { + target_id = "${var.module_name}_target" + rule = aws_cloudwatch_event_rule.lambda_rule.name + arn = aws_lambda_function.overpass_lambda.arn +} + +resource "aws_lambda_permission" "allow_cloudwatch_to_call_lambda" { + statement_id = "AllowExecutionFromCloudWatch" + action = "lambda:InvokeFunction" + function_name = aws_lambda_function.overpass_lambda.function_name + principal = "events.amazonaws.com" + source_arn = aws_cloudwatch_event_rule.lambda_rule.arn +} + +resource "aws_ecr_repository" "lambda_repository" { + name = "${var.module_name}-lambda" +} diff --git a/terraform/modules/alpr_clusters/outputs.tf b/terraform/modules/alpr_clusters/outputs.tf new file mode 100644 index 0000000..eb5dabe --- /dev/null +++ b/terraform/modules/alpr_clusters/outputs.tf @@ -0,0 +1,3 @@ +output "ecr_repository_url" { + value = aws_ecr_repository.lambda_repository.repository_url +} diff --git a/terraform/modules/alpr_clusters/variables.tf b/terraform/modules/alpr_clusters/variables.tf new file mode 100644 index 0000000..98d92fa --- /dev/null +++ b/terraform/modules/alpr_clusters/variables.tf @@ -0,0 +1,16 @@ +variable "module_name" { + description = "Name of the module" +} + +variable "output_filename" { + description = "Filename for the ALPR clusters JSON file" + default = "alpr_clusters.json" +} + +variable "deflock_stats_bucket" { + description = "S3 bucket for the ALPR clusters JSON file" +} + +variable "rate" { + description = "Rate at which to run the Lambda function" +} diff --git a/terraform/alpr-counts.tf b/terraform/modules/alpr_counts/main.tf similarity index 71% rename from terraform/alpr-counts.tf rename to terraform/modules/alpr_counts/main.tf index 5d0d136..2775a0e 100644 --- a/terraform/alpr-counts.tf +++ b/terraform/modules/alpr_counts/main.tf @@ -1,12 +1,3 @@ -locals { - alpr_counts_filename = "alpr-counts.json" -} - - -provider "aws" { - region = "us-east-1" -} - resource "aws_iam_role" "lambda_role" { name = "lambda_s3_write_role" @@ -26,7 +17,7 @@ resource "aws_iam_role" "lambda_role" { resource "aws_iam_policy" "lambda_s3_write_policy" { name = "lambda_s3_write_policy" - description = "Policy for Lambda to write to S3 bucket deflock-clusters" + description = "Policy for Lambda to write to S3 bucket ${var.deflock_stats_bucket}" policy = jsonencode({ Version = "2012-10-17" @@ -37,7 +28,7 @@ resource "aws_iam_policy" "lambda_s3_write_policy" { "s3:PutObjectAcl" ] Effect = "Allow" - Resource = "arn:aws:s3:::deflock-clusters/${local.alpr_counts_filename}" + Resource = "arn:aws:s3:::${var.deflock_stats_bucket}/${var.output_filename}" } ] }) @@ -51,43 +42,43 @@ resource "aws_iam_role_policy_attachment" "lambda_s3_write_attachment" { resource "null_resource" "pip_install" { provisioner "local-exec" { command = <