Building

Represents a building in Gothenburg, including its physical attributes and population data.

A building object refers to an existing building and includes
  • Footprint area
  • Total built-up area
  • Coordinates (in EPSG:3006 coordinate reference system)
  • Height of the building (calculated from laser point-cloud data)
  • Population per floor
  • Total feasible population for the building
  • Unique identifier
  • List of house objects contained within it
  • List of all people living in the building

Attributes:

Name Type Description
uuid UUID

Unique identifier for the building.

type str

Type of the building (e.g., residential, commercial).

area float

Total area of the building.

height float

Height of the building.

floors int

Number of floors in the building.

footprint Polygon

Footprint area of the building.

population_per_floor int

Population per floor in the building.

population_total int

Total population in the building.

built_up_area float

Total built-up area of the building.

houses List

List of house objects contained within the building.

workers int

Number of workers in the building.

worker_list List

List of workers in the building.

coord Point

Coordinates of the building's centroid.

preferred_locations Optional[PreferredLocations]

Preferred locations for the building.

Source code in tripsender\building.py
 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
class Building:
    """
    Represents a building in Gothenburg, including its physical attributes and population data.

    A building object refers to an existing building and includes:
        - Footprint area
        - Total built-up area
        - Coordinates (in EPSG:3006 coordinate reference system)
        - Height of the building (calculated from laser point-cloud data)
        - Population per floor
        - Total feasible population for the building
        - Unique identifier
        - List of house objects contained within it
        - List of all people living in the building

    Attributes:
        uuid (UUID): Unique identifier for the building.
        type (str): Type of the building (e.g., residential, commercial).
        area (float): Total area of the building.
        height (float): Height of the building.
        floors (int): Number of floors in the building.
        footprint (Polygon): Footprint area of the building.
        population_per_floor (int): Population per floor in the building.
        population_total (int): Total population in the building.
        built_up_area (float): Total built-up area of the building.
        houses (List): List of house objects contained within the building.
        workers (int): Number of workers in the building.
        worker_list (List): List of workers in the building.
        coord (Point): Coordinates of the building's centroid.
        preferred_locations (Optional[PreferredLocations]): Preferred locations for the building.
    """

    instances = []
    def __init__(self, building_type, building_area, building_height, building_floors, footprint,population_per_floor,built_up_area):
        if footprint == None:
            raise ValueError("Building footprint is None.")
        self.uuid = uuid.uuid4()
        self.type = building_type
        self.area = building_area
        self.height = building_height
        self.floors = building_floors
        self.footprint = footprint
        self.population_per_floor = population_per_floor
        self.population_total = 0
        self.built_up_area = built_up_area
        self.houses = []
        self.instances.append(self)
        self.workers: int = 0
        self.worker_list: List = []
        #self.isEmpty = True
        self.coord = footprint.centroid
        # Initialize the preferred locations for this building
        self.preferred_locations : Optional(PreferredLocations) = None

    @property
    def is_empty(self):
        return len(self.houses) == 0

    @classmethod
    def clear_instances(cls):
        cls.instances = []

    def __repr__(self):
        return f"A {self.type} building with {self.floors} floors and {self.population_total} people."

    def info(self):
        """Returns a dictionary with information about the building."""
        return {
            "Building UUID": self.uuid,
            "Building Type": self.type,
            "Building Area": self.area,
            "Building Height": self.height,
            "Building Floors": self.floors,
            "Building Footprint": self.footprint,
            "Population per Floor": self.population_per_floor,
            "Population Total": self.population_total,
            "Houses in Building": [house.info() for house in self.houses],
            "Built up Area": self.built_up_area,
            "Is building empty" : self.isEmpty,
            "Number of workers": self.workers,
        }

    @classmethod
    def instantiate_buildings(cls, gdf_residential: gpd.GeoDataFrame):
        """Instantiate building objects based on input data."""
        cls.clear_instances()
        for _, row in gdf_residential.iterrows():
            Building(
                row['byggnadsundergrupp'],
                row['area'],
                row['height'],
                row['floors'],
                row['geom'],
                row['population_per_floor'],
                row['BTA']
            )
        if len(cls.instances) == 0:
            raise ValueError("Unable to instantiate buildings.")

    def add_houses(self, house):

        self.houses.append(house)
        house.building = self
        self.population_total += len(house.household.members)
        #self.isEmpty = False
        house.building_uuid = self.uuid

    @classmethod
    def return_gdf(cls):
        """Returns a GeoDataFrame with all buildings."""
        gdf = gpd.GeoDataFrame()
        gdf['uuid'] = [building.uuid for building in cls.instances]
        gdf['type'] = [building.type for building in cls.instances]
        gdf['area'] = [building.area for building in cls.instances]
        gdf['height'] = [building.height for building in cls.instances]
        gdf['floors'] = [building.floors for building in cls.instances]
        gdf['footprint'] = [building.footprint for building in cls.instances]
        gdf['population_per_floor'] = [building.population_per_floor for building in cls.instances]
        gdf['population_total'] = [building.population_total for building in cls.instances]
        gdf['built_up_area'] = [building.built_up_area for building in cls.instances]
        gdf['workers'] = [building.workers for building in cls.instances]
        gdf['is_empty'] = [building.is_empty for building in cls.instances]
        gdf['building'] = [building for building in cls.instances]
        gdf['coord'] = [building.coord for building in cls.instances]
        gdf['preferred_locations'] = [building.preferred_locations for building in cls.instances]

        # Set geometry to footprint
        gdf = gdf.set_geometry('footprint')
        # Set crs to EPSG:3006
        gdf.crs = "EPSG:3006"
        # If there are no buildings raise an error
        if len(gdf) == 0:
            raise ValueError("There are no buildings in the simulation.")

        return gdf
    import contextily as ctx
    def plot(self):

        """
        Plots the building footprint on a map with a basemap using contextily.
        """
        # Create a GeoDataFrame
        gdf = gpd.GeoDataFrame({'geometry': [self.footprint]}, crs="EPSG:3006")

        # Convert the GeoDataFrame to the Web Mercator projection (used by most contextily basemaps)
        gdf = gdf.to_crs(epsg=3857)

        # Plotting
        fig, ax = plt.subplots(figsize=(10, 10))
        gdf.plot(ax=ax, alpha=0.5, color='blue')  # Adjust alpha and color to your preference

        # Add basemap with contextily
        ctx.add_basemap(ax)

        # Optionally set bounds
        ax.set_xlim([gdf.total_bounds[0] - 1000, gdf.total_bounds[2] + 1000])
        ax.set_ylim([gdf.total_bounds[1] - 1000, gdf.total_bounds[3] + 1000])

        ax.axis('off')  # Turn off axis
        plt.show()

info()

Returns a dictionary with information about the building.

Source code in tripsender\building.py
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
def info(self):
    """Returns a dictionary with information about the building."""
    return {
        "Building UUID": self.uuid,
        "Building Type": self.type,
        "Building Area": self.area,
        "Building Height": self.height,
        "Building Floors": self.floors,
        "Building Footprint": self.footprint,
        "Population per Floor": self.population_per_floor,
        "Population Total": self.population_total,
        "Houses in Building": [house.info() for house in self.houses],
        "Built up Area": self.built_up_area,
        "Is building empty" : self.isEmpty,
        "Number of workers": self.workers,
    }

instantiate_buildings(gdf_residential) classmethod

Instantiate building objects based on input data.

Source code in tripsender\building.py
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
@classmethod
def instantiate_buildings(cls, gdf_residential: gpd.GeoDataFrame):
    """Instantiate building objects based on input data."""
    cls.clear_instances()
    for _, row in gdf_residential.iterrows():
        Building(
            row['byggnadsundergrupp'],
            row['area'],
            row['height'],
            row['floors'],
            row['geom'],
            row['population_per_floor'],
            row['BTA']
        )
    if len(cls.instances) == 0:
        raise ValueError("Unable to instantiate buildings.")

plot()

Plots the building footprint on a map with a basemap using contextily.

Source code in tripsender\building.py
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
def plot(self):

    """
    Plots the building footprint on a map with a basemap using contextily.
    """
    # Create a GeoDataFrame
    gdf = gpd.GeoDataFrame({'geometry': [self.footprint]}, crs="EPSG:3006")

    # Convert the GeoDataFrame to the Web Mercator projection (used by most contextily basemaps)
    gdf = gdf.to_crs(epsg=3857)

    # Plotting
    fig, ax = plt.subplots(figsize=(10, 10))
    gdf.plot(ax=ax, alpha=0.5, color='blue')  # Adjust alpha and color to your preference

    # Add basemap with contextily
    ctx.add_basemap(ax)

    # Optionally set bounds
    ax.set_xlim([gdf.total_bounds[0] - 1000, gdf.total_bounds[2] + 1000])
    ax.set_ylim([gdf.total_bounds[1] - 1000, gdf.total_bounds[3] + 1000])

    ax.axis('off')  # Turn off axis
    plt.show()

return_gdf() classmethod

Returns a GeoDataFrame with all buildings.

Source code in tripsender\building.py
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
@classmethod
def return_gdf(cls):
    """Returns a GeoDataFrame with all buildings."""
    gdf = gpd.GeoDataFrame()
    gdf['uuid'] = [building.uuid for building in cls.instances]
    gdf['type'] = [building.type for building in cls.instances]
    gdf['area'] = [building.area for building in cls.instances]
    gdf['height'] = [building.height for building in cls.instances]
    gdf['floors'] = [building.floors for building in cls.instances]
    gdf['footprint'] = [building.footprint for building in cls.instances]
    gdf['population_per_floor'] = [building.population_per_floor for building in cls.instances]
    gdf['population_total'] = [building.population_total for building in cls.instances]
    gdf['built_up_area'] = [building.built_up_area for building in cls.instances]
    gdf['workers'] = [building.workers for building in cls.instances]
    gdf['is_empty'] = [building.is_empty for building in cls.instances]
    gdf['building'] = [building for building in cls.instances]
    gdf['coord'] = [building.coord for building in cls.instances]
    gdf['preferred_locations'] = [building.preferred_locations for building in cls.instances]

    # Set geometry to footprint
    gdf = gdf.set_geometry('footprint')
    # Set crs to EPSG:3006
    gdf.crs = "EPSG:3006"
    # If there are no buildings raise an error
    if len(gdf) == 0:
        raise ValueError("There are no buildings in the simulation.")

    return gdf

PreferredLocations

Represents a collection of preferred locations categorized by type.

Each attribute in this class represents a different type of preferred location. While some locations have only one preferred spot (e.g., schools), others can have multiple preferred spots (e.g., leisure locations).

Source code in tripsender\building.py
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
class PreferredLocations:
    """
    Represents a collection of preferred locations categorized by type.

    Each attribute in this class represents a different type of preferred location.
    While some locations have only one preferred spot (e.g., schools), others
    can have multiple preferred spots (e.g., leisure locations).
    """

    instances = []
    all_locations_coords = []  # Class-level list to store coordinates

    def __init__(self, locations: List[Location]):
        self.EDUCATION_förskola: Location = None
        self.EDUCATION_förskoleklass: Location = None
        self.EDUCATION_grundskola: Location = None
        self.EDUCATION_gymnasieskola: Location = None
        self.EDUCATION_fritidshem: Location = None
        self.LEISURE_sports: List[Location] = []
        self.LEISURE_playground: List[Location] = []
        self.EDUCATION: List[Location] = []
        self.SHOPPING_GROCERY: List[Location] = []
        self.SHOPPING_OTHER: List[Location] = []
        self.LEISURE: List[Location] = []
        self.HEALTHCARE: List[Location] = []
        self.origin: None
        self.instances.append(self)
        for location in locations:
            self.all_locations_coords.append(location.location_coordinates)

        for location in locations:
            if location.location_type == "EDUCATION_förskola":
                self.EDUCATION_förskola = location
            elif location.location_type == "EDUCATION_förskoleklass":
                self.EDUCATION_förskoleklass = location
            elif location.location_type == "EDUCATION_grundskola":
                self.EDUCATION_grundskola = location
            elif location.location_type == "EDUCATION_gymnasieskola":
                self.EDUCATION_gymnasieskola = location
            elif location.location_type == "EDUCATION_fritidshem":
                self.EDUCATION_fritidshem = location
            elif location.location_type == "LEISURE_sports":
                self.LEISURE_sports.append(location)
            elif location.location_type == "LEISURE_playground":
                self.LEISURE_playground.append(location)
            elif location.location_type == "EDUCATION":
                self.EDUCATION.append(location)
            elif location.location_type == "SHOPPING_OTHER":
                self.SHOPPING_OTHER.append(location)
            elif location.location_type == "LEISURE":
                self.LEISURE.append(location)
            elif location.location_type == "HEALTHCARE":
                self.HEALTHCARE.append(location)
            elif location.location_type == "SHOPPING_GROCERY":
                self.SHOPPING_GROCERY.append(location)

    def __repr__(self):
        return (
            f"Preferred locations for this household:\n"
            f"  EDUCATION_förskola: {self.EDUCATION_förskola}\n"
            f"  EDUCATION_förskoleklass: {self.EDUCATION_förskoleklass}\n"
            f"  EDUCATION_grundskola: {self.EDUCATION_grundskola}\n"
            f"  EDUCATION_gymnasieskola: {self.EDUCATION_gymnasieskola}\n"
            f"  EDUCATION_fritidshem: {self.EDUCATION_fritidshem}\n"
            f"  LEISURE_sports: {self.LEISURE_sports}\n"
            f"  LEISURE_playground: {self.LEISURE_playground}\n"
            f"  EDUCATION: {self.EDUCATION}\n"
            f"  SHOPPING_OTHER: {self.SHOPPING_OTHER}\n"
            f"  LEISURE: {self.LEISURE}\n"
            f"  HEALTHCARE: {self.HEALTHCARE}\n"
            f"  SHOPPING_GROCERY: {self.SHOPPING_GROCERY}\n"
            f"  origin: {self.origin}\n"
        )

    def get_dict(self):
        dictionary = {
            "EDUCATION_förskola": self.EDUCATION_förskola,
            "EDUCATION_förskoleklass": self.EDUCATION_förskoleklass,
            "EDUCATION_grundskola": self.EDUCATION_grundskola,
            "EDUCATION_gymnasieskola": self.EDUCATION_gymnasieskola,
            "EDUCATION_fritidshem": self.EDUCATION_fritidshem,
            "LEISURE_sports": self.LEISURE_sports,
            "LEISURE_playground": self.LEISURE_playground,
            "EDUCATION": self.EDUCATION,
            "SHOPPING_OTHER": self.SHOPPING_OTHER,
            "LEISURE": self.LEISURE,
            "HEALTHCARE": self.HEALTHCARE,
            "SHOPPING_GROCERY": self.SHOPPING_GROCERY,
            "origin": self.origin
        }
        return dictionary



    def random_location(self):
        """Returns a random preferred location."""
        # Extracting all locations into a flat list
        return random.choice(self.all_locations_coords)

    def return_gdf(self):
        """Returns a GeoDataFrame of the preferred locations."""
        # Extracting all locations into a flat list
        all_locations = []
        origin = self.origin
        for attr, value in self.__dict__.items():
            # Skip origin
            if attr == "origin":
                continue
            if isinstance(value, list):
                all_locations.extend(value)
            elif value is not None:
                all_locations.append(value)

        # Convert locations to GeoDataFrame
        gdf = gpd.GeoDataFrame({
            'LocationType': [loc.location_type for loc in all_locations],
            'geometry': [loc.location_coordinates for loc in all_locations]
        })

        # Add origin (Point) to GeoDataFrame
        gdf.loc[-1] = ["Origin", origin]

        # Set crs to EPSG:3006
        gdf.crs = "EPSG:3006"

        return gdf

    def plot(self,figsize=(10,10),ax=None):

        """Plots the preferred locations using different colors for each activity type."""
        gdf = self.return_gdf()

        # Defining colors for each location type for visualization
        colors = {
            "EDUCATION_förskola": "blue",
            "EDUCATION_förskoleklass": "cyan",
            "EDUCATION_grundskola": "green",
            "EDUCATION_gymnasieskola": "yellow",
            "EDUCATION_fritidshem": "purple",
            "LEISURE_sports": "red",
            "LEISURE_playground": "orange",
            "EDUCATION": "pink",
            "SHOPPING_GROCERY": "brown",
            "SHOPPING_OTHER": "gray",
            "LEISURE": "magenta",
            "HEALTHCARE": "black"
        }

        # Plotting
        if not ax:
            fig, ax = plt.subplots()
        for location_type, color in colors.items():
            gdf[gdf['LocationType'] == location_type].plot(ax=ax, color=color, label=location_type)

        ax.legend(loc="upper left")
        plt.title("Preferred Locations by Activity Type")
        plt.xlabel("Longitude")
        plt.ylabel("Latitude")
        plt.grid(True)
        plt.show()


    @classmethod
    def return_convex_hull(cls):
        """Returns the convex hull of all preferred locations."""

        # Convert list of coordinates to MultiPoint
        #Chcekc that all_locations_coords is not empty
        if len(cls.all_locations_coords) == 0:
            raise ValueError("There are no preferred locations in the simulation.")

        multi_point = MultiPoint(cls.all_locations_coords)

        # Return the convex hull
        return multi_point.convex_hull

plot(figsize=(10, 10), ax=None)

Plots the preferred locations using different colors for each activity type.

Source code in tripsender\building.py
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
def plot(self,figsize=(10,10),ax=None):

    """Plots the preferred locations using different colors for each activity type."""
    gdf = self.return_gdf()

    # Defining colors for each location type for visualization
    colors = {
        "EDUCATION_förskola": "blue",
        "EDUCATION_förskoleklass": "cyan",
        "EDUCATION_grundskola": "green",
        "EDUCATION_gymnasieskola": "yellow",
        "EDUCATION_fritidshem": "purple",
        "LEISURE_sports": "red",
        "LEISURE_playground": "orange",
        "EDUCATION": "pink",
        "SHOPPING_GROCERY": "brown",
        "SHOPPING_OTHER": "gray",
        "LEISURE": "magenta",
        "HEALTHCARE": "black"
    }

    # Plotting
    if not ax:
        fig, ax = plt.subplots()
    for location_type, color in colors.items():
        gdf[gdf['LocationType'] == location_type].plot(ax=ax, color=color, label=location_type)

    ax.legend(loc="upper left")
    plt.title("Preferred Locations by Activity Type")
    plt.xlabel("Longitude")
    plt.ylabel("Latitude")
    plt.grid(True)
    plt.show()

random_location()

Returns a random preferred location.

Source code in tripsender\building.py
291
292
293
294
def random_location(self):
    """Returns a random preferred location."""
    # Extracting all locations into a flat list
    return random.choice(self.all_locations_coords)

return_convex_hull() classmethod

Returns the convex hull of all preferred locations.

Source code in tripsender\building.py
359
360
361
362
363
364
365
366
367
368
369
370
371
@classmethod
def return_convex_hull(cls):
    """Returns the convex hull of all preferred locations."""

    # Convert list of coordinates to MultiPoint
    #Chcekc that all_locations_coords is not empty
    if len(cls.all_locations_coords) == 0:
        raise ValueError("There are no preferred locations in the simulation.")

    multi_point = MultiPoint(cls.all_locations_coords)

    # Return the convex hull
    return multi_point.convex_hull

return_gdf()

Returns a GeoDataFrame of the preferred locations.

Source code in tripsender\building.py
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
def return_gdf(self):
    """Returns a GeoDataFrame of the preferred locations."""
    # Extracting all locations into a flat list
    all_locations = []
    origin = self.origin
    for attr, value in self.__dict__.items():
        # Skip origin
        if attr == "origin":
            continue
        if isinstance(value, list):
            all_locations.extend(value)
        elif value is not None:
            all_locations.append(value)

    # Convert locations to GeoDataFrame
    gdf = gpd.GeoDataFrame({
        'LocationType': [loc.location_type for loc in all_locations],
        'geometry': [loc.location_coordinates for loc in all_locations]
    })

    # Add origin (Point) to GeoDataFrame
    gdf.loc[-1] = ["Origin", origin]

    # Set crs to EPSG:3006
    gdf.crs = "EPSG:3006"

    return gdf