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

This commit is contained in:
Helldragon67 2024-12-02 22:22:35 +01:00
parent 4397e36be6
commit 06d2f4c8aa
12 changed files with 149400 additions and 171473 deletions

View File

@ -440,6 +440,7 @@ disable=raw-checker-failed,
use-implicit-booleaness-not-comparison-to-string,
use-implicit-booleaness-not-comparison-to-zero,
import-error
line-too-long
# Enable the message, report, category or checker with the given id(s). You can
# either give multiple identifier separated by comma (,) or put this option

View File

@ -24,3 +24,4 @@ pywikibot = "*"
pymemcache = "*"
fastapi-cli = "*"
scikit-learn = "*"
pyqt6 = "*"

53
backend/Pipfile.lock generated
View File

@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
"sha256": "b24d98b1691997eecf51e5b29d9ea796278db573003ab06168ce7838911b5b69"
"sha256": "bb22b4e28c7aa199c94b688ad93d3ab0ccf1089a172131f4aec03b78e7bd7f1c"
},
"pipfile-spec": 6,
"requires": {},
@ -1092,6 +1092,57 @@
"markers": "python_version >= '3.9'",
"version": "==3.2.0"
},
"pyqt6": {
"hashes": [
"sha256:0adb7914c732ad1dee46d9cec838a98cb2b11bc38cc3b7b36fbd8701ae64bf47",
"sha256:2d771fa0981514cb1ee937633dfa64f14caa902707d9afffab66677f3a73e3da",
"sha256:3672a82ccd3a62e99ab200a13903421e2928e399fda25ced98d140313ad59cb9",
"sha256:7f397f4b38b23b5588eb2c0933510deb953d96b1f0323a916c4839c2a66ccccc",
"sha256:c2f202b7941aa74e5c7e1463a6f27d9131dbc1e6cabe85571d7364f5b3de7397",
"sha256:f053378e3aef6248fa612c8afddda17f942fb63f9fe8a9aeb2a6b6b4cbb0eba9",
"sha256:fa3954698233fe286a8afc477b84d8517f0788eb46b74da69d3ccc0170d3714c"
],
"index": "pypi",
"markers": "python_version >= '3.8'",
"version": "==6.7.1"
},
"pyqt6-qt6": {
"hashes": [
"sha256:36ea0892b8caeb983af3f285f45fb8dfbb93cfd972439f4e01b7efb2868f6230",
"sha256:50c7482bcdcf2bb78af257fb10ed8b582f8daf91d829782393bc50ac5a0a900c",
"sha256:8551732984fb36a5f4f3db51eafc4e8e6caf18617365830285306f2db17a94c2",
"sha256:cb525fdd393332de60887953029276a44de480fce1d785251ae639580f5e7246",
"sha256:f517a93b6b1a814d4aa6587adc312e812ebaf4d70415bb15cfb44268c5ad3f5f"
],
"version": "==6.7.3"
},
"pyqt6-sip": {
"hashes": [
"sha256:056af69d1d8d28d5968066ec5da908afd82fc0be07b67cf2b84b9f02228416ce",
"sha256:08dd81037a2864982ece2bf9891f3bf4558e247034e112993ea1a3fe239458cb",
"sha256:2559afa68825d08de09d71c42f3b6ad839dcc30f91e7c6d0785e07830d5541a5",
"sha256:2f74cf3d6d9cab5152bd9f49d570b2dfb87553ebb5c4919abfde27f5b9fd69d4",
"sha256:33d9b399fc9c9dc99496266842b0fb2735d924604774e97cf9b555667cc0fc59",
"sha256:6bce6bc5870d9e87efe5338b1ee4a7b9d7d26cdd16a79a5757d80b6f25e71edc",
"sha256:755beb5d271d081e56618fb30342cdd901464f721450495cb7cb0212764da89e",
"sha256:7a0bbc0918eab5b6351735d40cf22cbfa5aa2476b55e0d5fe881aeed7d871c29",
"sha256:7f84c472afdc7d316ff683f63129350d645ef82d9b3fd75a609b08472d1f7291",
"sha256:835ed22eab977f75fd77e60d4ff308a1fa794b1d0c04849311f36d2a080cdf3b",
"sha256:9ea9223c94906efd68148f12ae45b51a21d67e86704225ddc92bce9c54e4d93c",
"sha256:a5c086b7c9c7996ea9b7522646cc24eebbf3591ec9dd38f65c0a3fdb0dbeaac7",
"sha256:b1bf29e95f10a8a00819dac804ca7e5eba5fc1769adcd74c837c11477bf81954",
"sha256:b203b6fbae4a8f2d27f35b7df46200057033d9ecd9134bcf30e3eab66d43572c",
"sha256:beaddc1ec96b342f4e239702f91802706a80cb403166c2da318cec4ad8b790cb",
"sha256:cd81144b0770084e8005d3a121c9382e6f9bc8d0bb320dd618718ffe5090e0e6",
"sha256:cedd554c643e54c4c2e12b5874781a87441a1b405acf3650a4a2e1df42aae231",
"sha256:d8b22a6850917c68ce83fc152a8b606ecb2efaaeed35be53110468885d6cdd9d",
"sha256:dd168667addf01f8a4b0fa7755323e43e4cd12ca4bade558c61f713a5d48ba1a",
"sha256:f57275b5af774529f9838adcfb58869ba3ebdaf805daea113bb0697a96a3f3cb",
"sha256:fbb249b82c53180f1420571ece5dc24fea1188ba435923edd055599dffe7abfb"
],
"markers": "python_version >= '3.8'",
"version": "==13.8.0"
},
"python-dateutil": {
"hashes": [
"sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3",

View 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"
}
]
}

View File

@ -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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -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