working algo for clusters
	
		
			
	
		
	
	
		
	
		
			Some checks failed
		
		
	
	
		
			
				
	
				Build and deploy the backend to staging / Build and push image (pull_request) Failing after 2m17s
				
			
		
			
				
	
				Build and deploy the backend to staging / Deploy to staging (pull_request) Has been skipped
				
			
		
			
				
	
				Run linting on the backend code / Build (pull_request) Failing after 30s
				
			
		
			
				
	
				Run testing on the backend code / Build (pull_request) Failing after 1m28s
				
			
		
		
	
	
				
					
				
			
		
			Some checks failed
		
		
	
	Build and deploy the backend to staging / Build and push image (pull_request) Failing after 2m17s
				
			Build and deploy the backend to staging / Deploy to staging (pull_request) Has been skipped
				
			Run linting on the backend code / Build (pull_request) Failing after 30s
				
			Run testing on the backend code / Build (pull_request) Failing after 1m28s
				
			This commit is contained in:
		
							
								
								
									
										698
									
								
								backend/src/sandbox/colmar_data.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										698
									
								
								backend/src/sandbox/colmar_data.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,698 @@ | ||||
| { | ||||
|     "type": "FeatureCollection", | ||||
|     "generator": "overpass-turbo", | ||||
|     "copyright": "The data included in this document is from www.openstreetmap.org. The data is made available under ODbL.", | ||||
|     "timestamp": "2024-12-02T21:14:59Z", | ||||
|     "features": [ | ||||
|       { | ||||
|         "type": "Feature", | ||||
|         "properties": { | ||||
|           "@id": "node/1345741798", | ||||
|           "name": "Cordonnerie Saint-Joseph", | ||||
|           "shop": "shoes" | ||||
|         }, | ||||
|         "geometry": { | ||||
|           "type": "Point", | ||||
|           "coordinates": [ | ||||
|             7.3481705, | ||||
|             48.0816462 | ||||
|           ] | ||||
|         }, | ||||
|         "id": "node/1345741798" | ||||
|       }, | ||||
|       { | ||||
|         "type": "Feature", | ||||
|         "properties": { | ||||
|           "@id": "node/2659184738", | ||||
|           "brand": "Armand Thiery", | ||||
|           "brand:wikidata": "Q2861975", | ||||
|           "brand:wikipedia": "fr:Armand Thiery", | ||||
|           "name": "Armand Thiery", | ||||
|           "opening_hours": "Mo-Sa 09:30-19:00", | ||||
|           "shop": "clothes", | ||||
|           "wheelchair": "limited" | ||||
|         }, | ||||
|         "geometry": { | ||||
|           "type": "Point", | ||||
|           "coordinates": [ | ||||
|             7.3594454, | ||||
|             48.0785574 | ||||
|           ] | ||||
|         }, | ||||
|         "id": "node/2659184738" | ||||
|       }, | ||||
|       { | ||||
|         "type": "Feature", | ||||
|         "properties": { | ||||
|           "@id": "node/3618136290", | ||||
|           "name": "Chez Dominique", | ||||
|           "shop": "clothes" | ||||
|         }, | ||||
|         "geometry": { | ||||
|           "type": "Point", | ||||
|           "coordinates": [ | ||||
|             7.3362362, | ||||
|             48.0712174 | ||||
|           ] | ||||
|         }, | ||||
|         "id": "node/3618136290" | ||||
|       }, | ||||
|       { | ||||
|         "type": "Feature", | ||||
|         "properties": { | ||||
|           "@id": "node/3618136605", | ||||
|           "name": "Divamod", | ||||
|           "shop": "clothes" | ||||
|         }, | ||||
|         "geometry": { | ||||
|           "type": "Point", | ||||
|           "coordinates": [ | ||||
|             7.3304253, | ||||
|             48.0782989 | ||||
|           ] | ||||
|         }, | ||||
|         "id": "node/3618136605" | ||||
|       }, | ||||
|       { | ||||
|         "type": "Feature", | ||||
|         "properties": { | ||||
|           "@id": "node/3618284507", | ||||
|           "name": "Star tendances et voyages", | ||||
|           "shop": "clothes" | ||||
|         }, | ||||
|         "geometry": { | ||||
|           "type": "Point", | ||||
|           "coordinates": [ | ||||
|             7.3474029, | ||||
|             48.0830993 | ||||
|           ] | ||||
|         }, | ||||
|         "id": "node/3618284507" | ||||
|       }, | ||||
|       { | ||||
|         "type": "Feature", | ||||
|         "properties": { | ||||
|           "@id": "node/3619696125", | ||||
|           "brand": "Zeeman", | ||||
|           "brand:wikidata": "Q184399", | ||||
|           "name": "Zeeman", | ||||
|           "shop": "clothes" | ||||
|         }, | ||||
|         "geometry": { | ||||
|           "type": "Point", | ||||
|           "coordinates": [ | ||||
|             7.3413834, | ||||
|             48.0638444 | ||||
|           ] | ||||
|         }, | ||||
|         "id": "node/3619696125" | ||||
|       }, | ||||
|       { | ||||
|         "type": "Feature", | ||||
|         "properties": { | ||||
|           "@id": "node/4594398129", | ||||
|           "name": "Miss et Mister", | ||||
|           "shop": "clothes" | ||||
|         }, | ||||
|         "geometry": { | ||||
|           "type": "Point", | ||||
|           "coordinates": [ | ||||
|             7.3308309, | ||||
|             48.0779118 | ||||
|           ] | ||||
|         }, | ||||
|         "id": "node/4594398129" | ||||
|       }, | ||||
|       { | ||||
|         "type": "Feature", | ||||
|         "properties": { | ||||
|           "@id": "node/4907320441", | ||||
|           "brand": "Sergent Major", | ||||
|           "brand:wikidata": "Q62521738", | ||||
|           "clothes": "babies;children", | ||||
|           "name": "Sergent Major", | ||||
|           "opening_hours": "Mo-Sa 09:30-19:00", | ||||
|           "shop": "clothes", | ||||
|           "wheelchair": "no" | ||||
|         }, | ||||
|         "geometry": { | ||||
|           "type": "Point", | ||||
|           "coordinates": [ | ||||
|             7.359116, | ||||
|             48.0787229 | ||||
|           ] | ||||
|         }, | ||||
|         "id": "node/4907320441" | ||||
|       }, | ||||
|       { | ||||
|         "type": "Feature", | ||||
|         "properties": { | ||||
|           "@id": "node/4907364791", | ||||
|           "brand": "Armand Thiery", | ||||
|           "brand:wikidata": "Q2861975", | ||||
|           "brand:wikipedia": "fr:Armand Thiery", | ||||
|           "clothes": "women", | ||||
|           "name": "Armand Thiery", | ||||
|           "shop": "clothes" | ||||
|         }, | ||||
|         "geometry": { | ||||
|           "type": "Point", | ||||
|           "coordinates": [ | ||||
|             7.3601857, | ||||
|             48.0783373 | ||||
|           ] | ||||
|         }, | ||||
|         "id": "node/4907364791" | ||||
|       }, | ||||
|       { | ||||
|         "type": "Feature", | ||||
|         "properties": { | ||||
|           "@id": "node/4907385675", | ||||
|           "check_date": "2024-05-19", | ||||
|           "clothes": "children", | ||||
|           "name": "Du Pareil...au même", | ||||
|           "shop": "clothes" | ||||
|         }, | ||||
|         "geometry": { | ||||
|           "type": "Point", | ||||
|           "coordinates": [ | ||||
|             7.3604521, | ||||
|             48.0779726 | ||||
|           ] | ||||
|         }, | ||||
|         "id": "node/4907385675" | ||||
|       }, | ||||
|       { | ||||
|         "type": "Feature", | ||||
|         "properties": { | ||||
|           "@id": "node/4922191645", | ||||
|           "name": "Abilos", | ||||
|           "shop": "clothes" | ||||
|         }, | ||||
|         "geometry": { | ||||
|           "type": "Point", | ||||
|           "coordinates": [ | ||||
|             7.3566167, | ||||
|             48.0794136 | ||||
|           ] | ||||
|         }, | ||||
|         "id": "node/4922191645" | ||||
|       }, | ||||
|       { | ||||
|         "type": "Feature", | ||||
|         "properties": { | ||||
|           "@id": "node/4922191648", | ||||
|           "brand": "Esprit", | ||||
|           "brand:wikidata": "Q532746", | ||||
|           "brand:wikipedia": "en:Esprit Holdings", | ||||
|           "name": "Esprit", | ||||
|           "shop": "clothes" | ||||
|         }, | ||||
|         "geometry": { | ||||
|           "type": "Point", | ||||
|           "coordinates": [ | ||||
|             7.3554004, | ||||
|             48.0787549 | ||||
|           ] | ||||
|         }, | ||||
|         "id": "node/4922191648" | ||||
|       }, | ||||
|       { | ||||
|         "type": "Feature", | ||||
|         "properties": { | ||||
|           "@id": "node/4922191972", | ||||
|           "brand": "Guess", | ||||
|           "brand:wikidata": "Q2470307", | ||||
|           "brand:wikipedia": "en:Guess (clothing)", | ||||
|           "name": "Guess", | ||||
|           "shop": "clothes" | ||||
|         }, | ||||
|         "geometry": { | ||||
|           "type": "Point", | ||||
|           "coordinates": [ | ||||
|             7.355273, | ||||
|             48.0788003 | ||||
|           ] | ||||
|         }, | ||||
|         "id": "node/4922191972" | ||||
|       }, | ||||
|       { | ||||
|         "type": "Feature", | ||||
|         "properties": { | ||||
|           "@id": "node/4922192001", | ||||
|           "name": "Lingerie", | ||||
|           "shop": "clothes" | ||||
|         }, | ||||
|         "geometry": { | ||||
|           "type": "Point", | ||||
|           "coordinates": [ | ||||
|             7.3575453, | ||||
|             48.0779317 | ||||
|           ] | ||||
|         }, | ||||
|         "id": "node/4922192001" | ||||
|       }, | ||||
|       { | ||||
|         "type": "Feature", | ||||
|         "properties": { | ||||
|           "@id": "node/5359915869", | ||||
|           "name": "Al Assil", | ||||
|           "shop": "clothes" | ||||
|         }, | ||||
|         "geometry": { | ||||
|           "type": "Point", | ||||
|           "coordinates": [ | ||||
|             7.3305665, | ||||
|             48.0780902 | ||||
|           ] | ||||
|         }, | ||||
|         "id": "node/5359915869" | ||||
|       }, | ||||
|       { | ||||
|         "type": "Feature", | ||||
|         "properties": { | ||||
|           "@id": "node/9089360040", | ||||
|           "brand": "Grain de Malice", | ||||
|           "brand:wikidata": "Q66757157", | ||||
|           "clothes": "women", | ||||
|           "name": "Grain de Malice", | ||||
|           "shop": "clothes", | ||||
|           "short_name": "GDM" | ||||
|         }, | ||||
|         "geometry": { | ||||
|           "type": "Point", | ||||
|           "coordinates": [ | ||||
|             7.3593125, | ||||
|             48.0786234 | ||||
|           ] | ||||
|         }, | ||||
|         "id": "node/9089360040" | ||||
|       }, | ||||
|       { | ||||
|         "type": "Feature", | ||||
|         "properties": { | ||||
|           "@id": "node/9095193153", | ||||
|           "brand": "Undiz", | ||||
|           "brand:wikidata": "Q105306275", | ||||
|           "clothes": "underwear", | ||||
|           "name": "Undiz", | ||||
|           "shop": "clothes" | ||||
|         }, | ||||
|         "geometry": { | ||||
|           "type": "Point", | ||||
|           "coordinates": [ | ||||
|             7.3599579, | ||||
|             48.0782846 | ||||
|           ] | ||||
|         }, | ||||
|         "id": "node/9095193153" | ||||
|       }, | ||||
|       { | ||||
|         "type": "Feature", | ||||
|         "properties": { | ||||
|           "@id": "node/9095193154", | ||||
|           "branch": "Lingerie", | ||||
|           "brand": "RougeGorge", | ||||
|           "brand:wikidata": "Q104600739", | ||||
|           "clothes": "underwear", | ||||
|           "name": "RougeGorge", | ||||
|           "shop": "clothes" | ||||
|         }, | ||||
|         "geometry": { | ||||
|           "type": "Point", | ||||
|           "coordinates": [ | ||||
|             7.3604883, | ||||
|             48.0781607 | ||||
|           ] | ||||
|         }, | ||||
|         "id": "node/9095193154" | ||||
|       }, | ||||
|       { | ||||
|         "type": "Feature", | ||||
|         "properties": { | ||||
|           "@id": "node/9095212690", | ||||
|           "alt_name": "North Face", | ||||
|           "brand": "The North Face", | ||||
|           "brand:wikidata": "Q152784", | ||||
|           "brand:wikipedia": "en:The North Face", | ||||
|           "check_date": "2024-05-19", | ||||
|           "name": "The North Face", | ||||
|           "shop": "clothes" | ||||
|         }, | ||||
|         "geometry": { | ||||
|           "type": "Point", | ||||
|           "coordinates": [ | ||||
|             7.3603923, | ||||
|             48.0773727 | ||||
|           ] | ||||
|         }, | ||||
|         "id": "node/9095212690" | ||||
|       }, | ||||
|       { | ||||
|         "type": "Feature", | ||||
|         "properties": { | ||||
|           "@id": "node/9095270059", | ||||
|           "air_conditioning": "no", | ||||
|           "clothes": "men", | ||||
|           "level": "0", | ||||
|           "name": "Maison Aume", | ||||
|           "second_hand": "no", | ||||
|           "shop": "clothes", | ||||
|           "wheelchair": "no" | ||||
|         }, | ||||
|         "geometry": { | ||||
|           "type": "Point", | ||||
|           "coordinates": [ | ||||
|             7.361364, | ||||
|             48.0799999 | ||||
|           ] | ||||
|         }, | ||||
|         "id": "node/9095270059" | ||||
|       }, | ||||
|       { | ||||
|         "type": "Feature", | ||||
|         "properties": { | ||||
|           "@id": "node/9098624272", | ||||
|           "name": "Destock Place", | ||||
|           "shop": "clothes" | ||||
|         }, | ||||
|         "geometry": { | ||||
|           "type": "Point", | ||||
|           "coordinates": [ | ||||
|             7.3575161, | ||||
|             48.0793009 | ||||
|           ] | ||||
|         }, | ||||
|         "id": "node/9098624272" | ||||
|       }, | ||||
|       { | ||||
|         "type": "Feature", | ||||
|         "properties": { | ||||
|           "@id": "node/9123861652", | ||||
|           "name": "Weackers", | ||||
|           "shop": "shoes" | ||||
|         }, | ||||
|         "geometry": { | ||||
|           "type": "Point", | ||||
|           "coordinates": [ | ||||
|             7.361329, | ||||
|             48.0785972 | ||||
|           ] | ||||
|         }, | ||||
|         "id": "node/9123861652" | ||||
|       }, | ||||
|       { | ||||
|         "type": "Feature", | ||||
|         "properties": { | ||||
|           "@id": "node/9162179887", | ||||
|           "brand": "Calzedonia", | ||||
|           "brand:wikidata": "Q1027874", | ||||
|           "brand:wikipedia": "en:Calzedonia", | ||||
|           "name": "Calzedonia", | ||||
|           "shop": "clothes" | ||||
|         }, | ||||
|         "geometry": { | ||||
|           "type": "Point", | ||||
|           "coordinates": [ | ||||
|             7.3606374, | ||||
|             48.0780809 | ||||
|           ] | ||||
|         }, | ||||
|         "id": "node/9162179887" | ||||
|       }, | ||||
|       { | ||||
|         "type": "Feature", | ||||
|         "properties": { | ||||
|           "@id": "node/9162206449", | ||||
|           "clothes": "women", | ||||
|           "name": "Cop. Copine", | ||||
|           "shop": "clothes" | ||||
|         }, | ||||
|         "geometry": { | ||||
|           "type": "Point", | ||||
|           "coordinates": [ | ||||
|             7.3600947, | ||||
|             48.078399 | ||||
|           ] | ||||
|         }, | ||||
|         "id": "node/9162206449" | ||||
|       }, | ||||
|       { | ||||
|         "type": "Feature", | ||||
|         "properties": { | ||||
|           "@id": "node/9162226360", | ||||
|           "brand": "Okaïdi", | ||||
|           "brand:wikidata": "Q3350027", | ||||
|           "brand:wikipedia": "fr:Okaïdi", | ||||
|           "name": "Okaïdi", | ||||
|           "shop": "clothes" | ||||
|         }, | ||||
|         "geometry": { | ||||
|           "type": "Point", | ||||
|           "coordinates": [ | ||||
|             7.3596986, | ||||
|             48.078428 | ||||
|           ] | ||||
|         }, | ||||
|         "id": "node/9162226360" | ||||
|       }, | ||||
|       { | ||||
|         "type": "Feature", | ||||
|         "properties": { | ||||
|           "@id": "node/9162227010", | ||||
|           "brand": "Jules", | ||||
|           "brand:wikidata": "Q3188386", | ||||
|           "brand:wikipedia": "fr:Jules (enseigne)", | ||||
|           "clothes": "men", | ||||
|           "name": "Jules", | ||||
|           "opening_hours": "Mo-Sa 09:30-19:00", | ||||
|           "phone": "+33 3 89 41 03 62", | ||||
|           "shop": "clothes", | ||||
|           "website": "https://www.jules.com/fr-fr/magasins/1600133/" | ||||
|         }, | ||||
|         "geometry": { | ||||
|           "type": "Point", | ||||
|           "coordinates": [ | ||||
|             7.3600323, | ||||
|             48.0782229 | ||||
|           ] | ||||
|         }, | ||||
|         "id": "node/9162227010" | ||||
|       }, | ||||
|       { | ||||
|         "type": "Feature", | ||||
|         "properties": { | ||||
|           "@id": "node/10151865029", | ||||
|           "name": "Atelier Cinq", | ||||
|           "shop": "clothes" | ||||
|         }, | ||||
|         "geometry": { | ||||
|           "type": "Point", | ||||
|           "coordinates": [ | ||||
|             7.3571756, | ||||
|             48.0772657 | ||||
|           ] | ||||
|         }, | ||||
|         "id": "node/10151865029" | ||||
|       }, | ||||
|       { | ||||
|         "type": "Feature", | ||||
|         "properties": { | ||||
|           "@id": "node/10862176110", | ||||
|           "name": "L'hexagone", | ||||
|           "shop": "bag" | ||||
|         }, | ||||
|         "geometry": { | ||||
|           "type": "Point", | ||||
|           "coordinates": [ | ||||
|             7.3808571, | ||||
|             48.0814138 | ||||
|           ] | ||||
|         }, | ||||
|         "id": "node/10862176110" | ||||
|       }, | ||||
|       { | ||||
|         "type": "Feature", | ||||
|         "properties": { | ||||
|           "@id": "node/11150877331", | ||||
|           "brand": "Punt Roma", | ||||
|           "brand:wikidata": "Q101423290", | ||||
|           "clothes": "women", | ||||
|           "name": "Punt Roma", | ||||
|           "shop": "clothes" | ||||
|         }, | ||||
|         "geometry": { | ||||
|           "type": "Point", | ||||
|           "coordinates": [ | ||||
|             7.3571859, | ||||
|             48.0779406 | ||||
|           ] | ||||
|         }, | ||||
|         "id": "node/11150877331" | ||||
|       }, | ||||
|       { | ||||
|         "type": "Feature", | ||||
|         "properties": { | ||||
|           "@id": "node/11150959880", | ||||
|           "name": "Caroll", | ||||
|           "shop": "clothes" | ||||
|         }, | ||||
|         "geometry": { | ||||
|           "type": "Point", | ||||
|           "coordinates": [ | ||||
|             7.3579354, | ||||
|             48.0779291 | ||||
|           ] | ||||
|         }, | ||||
|         "id": "node/11150959880" | ||||
|       }, | ||||
|       { | ||||
|         "type": "Feature", | ||||
|         "properties": { | ||||
|           "@id": "node/11302242094", | ||||
|           "branch": "Wintzenheim", | ||||
|           "name": "Label Fripe", | ||||
|           "opening_hours": "Mo-Sa 09:00-18:45", | ||||
|           "phone": "+33 3 89 27 39 25", | ||||
|           "second_hand": "only", | ||||
|           "shop": "clothes", | ||||
|           "website": "https://labelfripe.fr/label-fripe-wintzenheim/" | ||||
|         }, | ||||
|         "geometry": { | ||||
|           "type": "Point", | ||||
|           "coordinates": [ | ||||
|             7.3109899, | ||||
|             48.0850362 | ||||
|           ] | ||||
|         }, | ||||
|         "id": "node/11302242094" | ||||
|       }, | ||||
|       { | ||||
|         "type": "Feature", | ||||
|         "properties": { | ||||
|           "@id": "node/11392247003", | ||||
|           "name": "Lingerie Sipp", | ||||
|           "shop": "clothes" | ||||
|         }, | ||||
|         "geometry": { | ||||
|           "type": "Point", | ||||
|           "coordinates": [ | ||||
|             7.3111507, | ||||
|             48.0841835 | ||||
|           ] | ||||
|         }, | ||||
|         "id": "node/11392247003" | ||||
|       }, | ||||
|       { | ||||
|         "type": "Feature", | ||||
|         "properties": { | ||||
|           "@id": "node/11778819781", | ||||
|           "addr:city": "Colmar", | ||||
|           "addr:housenumber": "10", | ||||
|           "addr:postcode": "68000", | ||||
|           "addr:street": "Rue des Têtes", | ||||
|           "clothes": "suits;hats;men", | ||||
|           "name": "Phillipe", | ||||
|           "phone": "0389411983", | ||||
|           "shop": "clothes" | ||||
|         }, | ||||
|         "geometry": { | ||||
|           "type": "Point", | ||||
|           "coordinates": [ | ||||
|             7.3559389, | ||||
|             48.0789064 | ||||
|           ] | ||||
|         }, | ||||
|         "id": "node/11778819781" | ||||
|       }, | ||||
|       { | ||||
|         "type": "Feature", | ||||
|         "properties": { | ||||
|           "@id": "node/11799215969", | ||||
|           "brand": "Petit Bateau", | ||||
|           "brand:wikidata": "Q3377090", | ||||
|           "name": "Petit Bateau", | ||||
|           "opening_hours": "Mo-Sa 10:00-19:00; Su 10:00-18:00", | ||||
|           "phone": "+33 3 89 24 97 85", | ||||
|           "shop": "clothes", | ||||
|           "website": "https://stores.petit-bateau.com/france/colmar/9-rue-des-boulangers" | ||||
|         }, | ||||
|         "geometry": { | ||||
|           "type": "Point", | ||||
|           "coordinates": [ | ||||
|             7.355149, | ||||
|             48.0780213 | ||||
|           ] | ||||
|         }, | ||||
|         "id": "node/11799215969" | ||||
|       }, | ||||
|       { | ||||
|         "type": "Feature", | ||||
|         "properties": { | ||||
|           "@id": "node/11816704669", | ||||
|           "addr:housenumber": "10", | ||||
|           "addr:street": "Rue des Boulangers", | ||||
|           "name": "des petits hauts", | ||||
|           "shop": "clothes" | ||||
|         }, | ||||
|         "geometry": { | ||||
|           "type": "Point", | ||||
|           "coordinates": [ | ||||
|             7.3555001, | ||||
|             48.0780768 | ||||
|           ] | ||||
|         }, | ||||
|         "id": "node/11816704669" | ||||
|       }, | ||||
|       { | ||||
|         "type": "Feature", | ||||
|         "properties": { | ||||
|           "@id": "node/12320343534", | ||||
|           "addr:city": "Colmar", | ||||
|           "addr:housenumber": "44", | ||||
|           "addr:postcode": "68000", | ||||
|           "addr:street": "Rue des Clefs", | ||||
|           "brand": "Un Jour Ailleurs", | ||||
|           "brand:wikidata": "Q105106211", | ||||
|           "clothes": "women", | ||||
|           "name": "Un Jour Ailleurs", | ||||
|           "opening_hours": "Mo-Fr 10:00-19:00; Sa 10:00-18:30", | ||||
|           "phone": "+33368318572", | ||||
|           "shop": "clothes", | ||||
|           "website": "https://boutique.unjourailleurs.com/fr/mode-femme/boutique-colmar-76" | ||||
|         }, | ||||
|         "geometry": { | ||||
|           "type": "Point", | ||||
|           "coordinates": [ | ||||
|             7.35897, | ||||
|             48.0789807 | ||||
|           ] | ||||
|         }, | ||||
|         "id": "node/12320343534" | ||||
|       }, | ||||
|       { | ||||
|         "type": "Feature", | ||||
|         "properties": { | ||||
|           "@id": "node/12320343536", | ||||
|           "addr:city": "Colmar", | ||||
|           "addr:housenumber": "38", | ||||
|           "addr:postcode": "68000", | ||||
|           "addr:street": "Rue des Clefs", | ||||
|           "brand": "Timberland", | ||||
|           "brand:wikidata": "Q1539185", | ||||
|           "name": "Timberland", | ||||
|           "opening_hours": "Mo-Sa 10:00-19:00", | ||||
|           "phone": "+33389298650", | ||||
|           "shop": "clothes" | ||||
|         }, | ||||
|         "geometry": { | ||||
|           "type": "Point", | ||||
|           "coordinates": [ | ||||
|             7.3592409, | ||||
|             48.0788785 | ||||
|           ] | ||||
|         }, | ||||
|         "id": "node/12320343536" | ||||
|       } | ||||
|     ] | ||||
|   } | ||||
| @@ -1,4 +1,210 @@ | ||||
| import numpy as np | ||||
| import json | ||||
| import os | ||||
| from typing import Optional, Literal | ||||
| from sklearn.cluster import DBSCAN | ||||
| from sklearn.linear_model import RANSACRegressor | ||||
| from sklearn.decomposition import PCA | ||||
| import matplotlib.pyplot as plt | ||||
| from pydantic import BaseModel | ||||
|  | ||||
|  | ||||
|  | ||||
| class ShoppingLocation(BaseModel): | ||||
|     type: Literal['street', 'area'] | ||||
|     importance: int | ||||
|     centroid: tuple | ||||
|     start: Optional[list] = None | ||||
|     end: Optional[list] = None | ||||
|     end: Optional[tuple] = None | ||||
|  | ||||
|  | ||||
|  | ||||
| def extract_points(filestr: str) : | ||||
|     """ | ||||
|     Extract points from geojson file. | ||||
|  | ||||
|     Returns : | ||||
|         np.array containing the points | ||||
|     """ | ||||
|     points = [] | ||||
|  | ||||
|     with open(os.path.dirname(__file__) + '/' + filestr, 'r') as f: | ||||
|         geojson = json.load(f) | ||||
|  | ||||
|         for feature in geojson['features']: | ||||
|             if feature['geometry']['type'] == 'Point': | ||||
|                 centroid = feature['geometry']['coordinates'] | ||||
|                 points.append(centroid) | ||||
|  | ||||
|             elif feature['geometry']['type'] == 'Polygon': | ||||
|                 centroid = np.array(feature['geometry']['coordinates'][0][0]) | ||||
|                 points.append(centroid) | ||||
|      | ||||
|     # Convert the list of points to a NumPy array | ||||
|     return np.array(points) | ||||
|  | ||||
|  | ||||
| def filter_clusters(cluster_points, cluster_labels): | ||||
|     """ | ||||
|     Remove clusters of less importance. | ||||
|     """ | ||||
|     label_counts = np.bincount(cluster_labels) | ||||
|  | ||||
|     # Step 3: Get the indices (labels) of the 5 largest clusters | ||||
|     top_5_labels = np.argsort(label_counts)[-5:]  # Get the largest 5 clusters | ||||
|  | ||||
|     # Step 4: Filter points to keep only the points in the top 5 clusters | ||||
|     filtered_cluster_points = [] | ||||
|     filtered_cluster_labels = [] | ||||
|  | ||||
|     for label in top_5_labels: | ||||
|         filtered_cluster_points.append(cluster_points[cluster_labels == label]) | ||||
|         filtered_cluster_labels.append(np.full((label_counts[label],), label))  # Replicate the label | ||||
|  | ||||
|     # Concatenate filtered clusters into a single array | ||||
|     return np.vstack(filtered_cluster_points), np.concatenate(filtered_cluster_labels) | ||||
|  | ||||
|  | ||||
| def fit_lines(points, labels): | ||||
|     """ | ||||
|     Fit lines to identified clusters. | ||||
|     """ | ||||
|     all_x = [] | ||||
|     all_y = [] | ||||
|     lines = [] | ||||
|     locations = [] | ||||
|  | ||||
|     for label in set(labels): | ||||
|         cluster_points = points[labels == label] | ||||
|  | ||||
|         # If there's not enough points, skip | ||||
|         if len(cluster_points) < 2: | ||||
|             continue | ||||
|  | ||||
|         # Apply PCA to find the principal component (i.e., the line of best fit) | ||||
|         pca = PCA(n_components=1) | ||||
|         pca.fit(cluster_points) | ||||
|  | ||||
|         direction = pca.components_[0] | ||||
|         centroid = pca.mean_ | ||||
|  | ||||
|         # Project the cluster points onto the principal direction (line direction) | ||||
|         projections = np.dot(cluster_points - centroid, direction) | ||||
|  | ||||
|         # Get the range of the projections to find the approximate length of the cluster | ||||
|         cluster_length = projections.max() - projections.min() | ||||
|  | ||||
|         # Now adjust `t` so that it scales with the cluster length | ||||
|         t = np.linspace(-cluster_length / 2.75, cluster_length / 2.75, 10) | ||||
|  | ||||
|         # Calculate the start and end of the line based on min/max projections | ||||
|         start_point = centroid[0] + t*direction[0] | ||||
|         end_point = centroid[1] + t*direction[1] | ||||
|          | ||||
|         # Store the line | ||||
|         lines.append((start_point, end_point)) | ||||
|  | ||||
|         # For visualization, store the points | ||||
|         all_x.append(min(start_point)) | ||||
|         all_x.append(max(start_point)) | ||||
|         all_y.append(min(end_point)) | ||||
|         all_y.append(max(end_point)) | ||||
|  | ||||
|         if np.linalg.norm(t) <= 0.0045 : | ||||
|             loc = ShoppingLocation( | ||||
|                 type='area', | ||||
|                 centroid=tuple(centroid), | ||||
|                 importance = len(cluster_points) | ||||
|             ) | ||||
|         else : | ||||
|             loc = ShoppingLocation( | ||||
|                 type='street', | ||||
|                 centroid=tuple(centroid), | ||||
|                 start=start_point, | ||||
|                 end=end_point, | ||||
|                 importance = len(cluster_points) | ||||
|             ) | ||||
|  | ||||
|         locations.append(loc) | ||||
|  | ||||
|     xmin = min(all_x) | ||||
|     xmax = max(all_x) | ||||
|     ymin = min(all_y) | ||||
|     ymax = max(all_y) | ||||
|     corners = (xmin, xmax, ymin, ymax) | ||||
|  | ||||
|     return corners, locations | ||||
|  | ||||
|  | ||||
|  | ||||
| # Extract points | ||||
| points = extract_points('strasbourg_data.json') | ||||
|  | ||||
| # Create a figure with 1 row and 3 columns for side-by-side plots | ||||
| fig, axes = plt.subplots(1, 3, figsize=(15, 5)) | ||||
|  | ||||
| # Plot 0: Raw data points | ||||
| axes[0].set_title('Raw Data') | ||||
| axes[0].scatter(points[:, 0], points[:, 1], color='blue', s=20) | ||||
|  | ||||
| # Apply DBSCAN to find clusters. Choose different settings for different cities. | ||||
| if len(points) > 400 : | ||||
|     dbscan = DBSCAN(eps=0.00118, min_samples=15, algorithm='kd_tree')  # for large cities | ||||
| else : | ||||
|     dbscan = DBSCAN(eps=0.00075, min_samples=10, algorithm='kd_tree')  # for small cities | ||||
|  | ||||
| labels = dbscan.fit_predict(points) | ||||
|  | ||||
| # Separate clustered points and noise points | ||||
| clustered_points = points[labels != -1] | ||||
| clustered_labels = labels[labels != -1] | ||||
| noise_points = points[labels == -1]  | ||||
|  | ||||
| # Plot n°1: DBSCAN Clustering Results | ||||
| axes[1].set_title('DBSCAN Clusters') | ||||
| axes[1].scatter(clustered_points[:, 0], clustered_points[:, 1], c=clustered_labels, cmap='rainbow', s=20) | ||||
| axes[1].scatter(noise_points[:, 0], noise_points[:, 1], c='blue', s=7, label='Noise') | ||||
|  | ||||
| clustered_points, clustered_labels = filter_clusters(clustered_points, clustered_labels) | ||||
|  | ||||
|  | ||||
|  | ||||
| # Fit lines | ||||
| corners, locations = fit_lines(clustered_points, clustered_labels) | ||||
| (xmin, xmax, ymin, ymax) = corners | ||||
|  | ||||
|  | ||||
| # Plot clustered points in normal size and noise points separately | ||||
| axes[2].scatter(clustered_points[:, 0], clustered_points[:, 1], c=clustered_labels, cmap='rainbow', s=30) | ||||
| # axes[2].scatter(noise_points[:, 0], noise_points[:, 1], c='blue', s=10, label='Noise') | ||||
|  | ||||
| # Step 4: Plot the detected lines in the final plot | ||||
| for loc in locations: | ||||
|      | ||||
|     if loc.type == 'street' : | ||||
|         line_x = loc.start | ||||
|         line_y = loc.end | ||||
|         axes[2].plot(line_x, line_y, color='lime', linewidth=3) | ||||
|     else : | ||||
|         axes[2].scatter(loc.centroid[0], loc.centroid[1], color='None', edgecolors='lime', s=200, linewidth=3) | ||||
|     # print(8) | ||||
|  | ||||
| axes[2].set_title('PCA Fitted Lines on Clusters') | ||||
|      | ||||
| # print(all_x) | ||||
|  | ||||
| # Adjust the axis limit for previous plots | ||||
| axes[0].set_xlim(xmin-0.01, xmax+0.01) | ||||
| axes[0].set_ylim(ymin-0.01, ymax+0.01) | ||||
|  | ||||
| axes[1].set_xlim(xmin-0.01, xmax+0.01) | ||||
| axes[1].set_ylim(ymin-0.01, ymax+0.01) | ||||
|  | ||||
| axes[2].set_xlim(xmin-0.01, xmax+0.01) | ||||
| axes[2].set_ylim(ymin-0.01, ymax+0.01) | ||||
|  | ||||
| # Adjust layout for better spacing | ||||
| plt.tight_layout() | ||||
|  | ||||
| # Show the plots | ||||
| plt.show() | ||||
|   | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										4947
									
								
								backend/src/sandbox/strasbourg_data.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4947
									
								
								backend/src/sandbox/strasbourg_data.json
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										23140
									
								
								backend/src/sandbox/vienna_data.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23140
									
								
								backend/src/sandbox/vienna_data.json
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										2844
									
								
								backend/src/sandbox/winterthur_data.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2844
									
								
								backend/src/sandbox/winterthur_data.json
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -33,19 +33,19 @@ def invalid_client(): | ||||
|         ([91, 181], {"sightseeing": {"type": "nature", "score": 5}, | ||||
|                      "nature": {"type": "nature", "score": 5}, | ||||
|                      "shopping": {"type": "shopping", "score": 5}, | ||||
|                     }, 423), | ||||
|                     }, 422), | ||||
|         ([-91, 181], {"sightseeing": {"type": "nature", "score": 5}, | ||||
|                      "nature": {"type": "nature", "score": 5}, | ||||
|                      "shopping": {"type": "shopping", "score": 5}, | ||||
|                     }, 423), | ||||
|                     }, 422), | ||||
|         ([91, -181], {"sightseeing": {"type": "nature", "score": 5}, | ||||
|                      "nature": {"type": "nature", "score": 5}, | ||||
|                      "shopping": {"type": "shopping", "score": 5}, | ||||
|                     }, 423), | ||||
|                     }, 422), | ||||
|         ([-91, -181], {"sightseeing": {"type": "nature", "score": 5}, | ||||
|                      "nature": {"type": "nature", "score": 5}, | ||||
|                      "shopping": {"type": "shopping", "score": 5}, | ||||
|                     }, 423), | ||||
|                     }, 422), | ||||
|     ] | ||||
| ) | ||||
| def test_input(invalid_client, start, preferences, status_code):   # pylint: disable=redefined-outer-name | ||||
|   | ||||
		Reference in New Issue
	
	Block a user