File size: 17,910 Bytes
eda0b1c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4508e72
 
eda0b1c
c5b678e
 
 
 
 
 
eda0b1c
9670947
 
640f9c6
 
eda0b1c
2e84e69
 
 
 
 
 
 
e07f8da
 
cbbdd13
eda0b1c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0a00112
166b945
 
 
 
fda2c77
166b945
 
 
 
 
b06d934
166b945
b06d934
 
166b945
 
 
f69761c
166b945
 
 
 
 
63a0234
 
0f28441
c7a9ea2
63a0234
 
166b945
63a0234
166b945
30304df
166b945
83b2547
 
166b945
 
 
f69761c
166b945
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
962d621
166b945
2e84e69
 
 
 
 
 
 
 
29f0d25
2e84e69
29f0d25
2e84e69
 
7da3f75
2e84e69
 
 
 
 
 
 
 
 
 
 
 
 
 
7901797
2e84e69
 
 
 
 
 
 
 
 
 
 
 
 
 
8da1c2e
83cab62
eda0b1c
 
cc63a78
9d78ab1
b224536
9d78ab1
 
 
 
 
 
 
 
 
4c009bd
9d78ab1
 
 
 
07ba2d7
 
9d78ab1
3dea01b
9d78ab1
 
4c009bd
9d78ab1
 
 
 
07ba2d7
 
 
 
 
 
 
 
 
 
 
9d78ab1
3dea01b
9d78ab1
 
 
4c009bd
9d78ab1
 
 
 
4c009bd
9d78ab1
 
 
 
 
4c009bd
3cc6a9a
9d78ab1
 
 
 
 
 
4c009bd
3cc6a9a
9d78ab1
 
 
 
 
 
 
 
 
 
cc63a78
 
 
 
 
 
 
 
 
 
 
 
 
a68aa72
 
 
 
77e01dc
 
e1ff7af
79bcc66
e1ff7af
 
 
a68aa72
 
 
 
df92571
521213c
 
 
 
df92571
 
 
87f9d6f
 
 
 
 
 
 
 
521213c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a68aa72
 
a173230
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d4666b7
 
 
 
a5fefb6
d4666b7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31c3bf8
d4666b7
 
a173230
a68aa72
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
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
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
import sys

from specklepy.api.client import SpeckleClient
from specklepy.api.credentials import get_default_account, get_local_accounts
from specklepy.transports.server import ServerTransport
from specklepy.api import operations
from specklepy.objects.geometry import Polyline, Point
from specklepy.objects import Base

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import math
import matplotlib
import json

from notion_client import Client
import os

from config import landuseColumnName 
from config import subdomainColumnName 
from config import sqmPerEmployeeColumnName
from config import thresholdsColumnName 
from config import maxPointsColumnName
from config import domainColumnName 

from config import landuseDatabaseId , streamId,  dmBranchName, dmCommitId, luBranchName, luCommitId

import speckle_utils
import data_utils


notionToken = os.getenv('notionToken')
speckleToken = os.getenv('speckleToken')




# ----------------------------------------------------------------------------------



# query full database
def fetch_all_database_pages(client, database_id):
    """
    Fetches all pages from a specified Notion database.

    :param client: Initialized Notion client.
    :param database_id: The ID of the Notion database to query.
    :return: A list containing all pages from the database.
    """
    start_cursor = None
    all_pages = []

    while True:
        response = client.databases.query(
            **{
                "database_id": database_id,
                "start_cursor": start_cursor
            }
        )

        all_pages.extend(response['results'])

        # Check if there's more data to fetch
        if response['has_more']:
            start_cursor = response['next_cursor']
        else:
            break

    return all_pages



def get_property_value(page, property_name):
    """
    Extracts the value from a specific property in a Notion page based on its type.
    :param page: The Notion page data as retrieved from the API.
    :param property_name: The name of the property whose value is to be fetched.
    :return: The value or values contained in the specified property, depending on type.
    """
    # Check if the property exists in the page
    if property_name not in page['properties']:
        return None  # or raise an error if you prefer

    property_data = page['properties'][property_name]
    prop_type = property_data['type']

    # Handle 'title' and 'rich_text' types
    if prop_type in ['title', 'rich_text']:
        return ''.join(text_block['text']['content'] for text_block in property_data[prop_type])

    # Handle 'number' type
    elif prop_type == 'number':
        return property_data[prop_type]

    # Handle 'select' type
    elif prop_type == 'select':
        return property_data[prop_type]['name'] if property_data[prop_type] else None

    # Handle 'multi_select' type
    elif prop_type == 'multi_select':
        return [option['name'] for option in property_data[prop_type]]

    # Handle 'date' type
    elif prop_type == 'date':
        if property_data[prop_type]['end']:
            return (property_data[prop_type]['start'], property_data[prop_type]['end'])
        else:
            return property_data[prop_type]['start']

    # Handle 'relation' type
    elif prop_type == 'relation':
        return [relation['id'] for relation in property_data[prop_type]]

    # Handle 'people' type
    elif prop_type == 'people':
        return [person['name'] for person in property_data[prop_type] if 'name' in person]

    # Add more handlers as needed for other property types

    else:
        # Return None or raise an error for unsupported property types
        return None



def get_page_by_id(notion_db_pages, page_id):
  for pg in notion_db_pages:
    if pg["id"] == page_id:
      return pg


# --------------------------------------------------------------------------------------------- #


def getDataFromSpeckle(
    speckleClient,
	streamID,
	matrixBranchName, 
    landuseBranchName, 
	matrixComitID="", 
	landuseComitID="",     
	pathToData = ["@Data", "@{0}"],
	uuidColumn = "uuid", 
	landuseColumns="lu+"
    ):
	

    if landuseBranchName:
        streamLanduses = speckle_utils.getSpeckleStream(streamId,luBranchName,speckleClient, luCommitId)
        streamData = streamLanduses["@Data"]["@{0}"]
        
        dfLanduses = speckle_utils.get_dataframe(streamData, return_original_df=False)
        dfLanduses =  dfLanduses.set_index("uuid", drop=False)  # variable, uuid as default
        
        if type(landuseColumns) == type("s"):
            # extract landuse columns with "landuseColumns"
            landuse_columns = []   
            for name in dfLanduses.columns:
                if name.startswith(landuseColumns):
                    landuse_columns.append(name) 
            
        elif type(landuseColumns) == type([]):
    		#assmuming the user provided a lsit of columns 
            landuse_columns = landuseColumns

        dfLanduses_filtered = dfLanduses[landuse_columns]
        dfLanduses_filtered.columns = [col.replace('lu+', '') for col in dfLanduses_filtered.columns]


    if matrixBranchName: 
        streamObj = speckle_utils.getSpeckleStream(streamId,dmBranchName,speckleClient, dmCommitId)

        matrices = {}
        isDict = False     
        try:
            data_part = streamObj["@Data"]["@{0}"]
            for matrix in data_part:
                # Find the matrix name
                matrix_name = next((attr for attr in dir(matrix) if "matrix" in attr), None)
     
                if not matrix_name:
                    continue
     
                matrix_data = getattr(matrix, matrix_name)
                originUUID = matrix_data["@originUUID"]
                destinationUUID = matrix_data["@destinationUUID"]
     
                processed_rows = []
                for chunk in matrix_data["@chunks"]:
                    for row in chunk["@rows"]:
                        processed_rows.append(row["@row"])
     
                matrix_array = np.array(processed_rows)
                matrix_df = pd.DataFrame(matrix_array, index=originUUID, columns=destinationUUID)
                matrices[matrix_name] = matrix_df
        except KeyError:
            data_part = streamObj["@Data"].__dict__
            print(data_part.keys())
     
            for k, v in data_part.items():
                if "matrix" in k:
                    matrix_name = k
                    matrix_data = v
                    originUUID = matrix_data["@originUUID"]
                    destinationUUID = matrix_data["@destinationUUID"]
     
                    processed_rows = []
                    for chunk in matrix_data["@chunks"]:
                        for row in chunk["@rows"]:
                            processed_rows.append(row["@row"])
     
                    matrix_array = np.array(processed_rows)
                    matrix_df = pd.DataFrame(matrix_array, index=originUUID, columns=destinationUUID)
                    matrices[matrix_name] = matrix_df
     


    return dfLanduses_filtered, matrices



def getDataFromNotion(
        notion,
		notionToken,
		landuseDatabaseID,
		subdomainDatabaseID,
		landuseColumnName ="LANDUSE",
		subdomainColumnName ="SUBDOMAIN_LIVABILITY",
		sqmPerEmployeeColumnName = "SQM PER EMPL",
		thresholdsColumnName="MANHATTAN THRESHOLD",
		maxPointsColumnName = "LIVABILITY MAX POINT",
		domainColumnName = "DOMAIN_LIVABILITY"
		
		):

    landuse_attributes  = fetch_all_database_pages(notion, landuseDatabaseID)
    livability_attributes  = fetch_all_database_pages(notion, subdomainDatabaseID)
    landuseMapperDict ={}
    livabilityMapperDict ={}

    for page in landuse_attributes:
        value_landuse = get_property_value(page, landuseColumnName)
        value_subdomain = get_property_value(page, subdomainColumnName)
        origin =  "false" if not get_property_value(page, "is_origin_mask") else get_property_value(page, "is_origin_mask")
        if value_subdomain and value_landuse:
            landuseMapperDict[value_landuse] = {
            'subdomain livability': value_subdomain,
            'is origin': origin
            }
    
    for page in livability_attributes:
        subdomain = get_property_value(page, subdomainColumnName)
        sqm_per_employee = get_property_value(page, sqmPerEmployeeColumnName)
        thresholds = get_property_value(page, thresholdsColumnName)
        max_points = get_property_value(page, maxPointsColumnName)
        domain = get_property_value(page, domainColumnName)
        if  thresholds:   
            livabilityMapperDict[subdomain] = {
            'sqmPerEmpl': sqm_per_employee if sqm_per_employee != "" else 0,
            'thresholds': thresholds,
            'max_points': max_points,
            'domain': [domain if domain != "" else 0]
            }    
        
    return landuseMapperDict, livabilityMapperDict




def getDataFromGrasshopper(
        inputJson,
        inputNameMatrix,
        inputNameLanduse,
        inputNameAttributeMapper,
        inputNameLanduseMapper,        
        inputNameAlpha = "alpha",
        inputNameThreshold = "threshold"    
        ):

    if inputNameMatrix is not None:
        matrix = inputJson['input'][inputNameMatrix]
        dfMatrix_gh = pd.DataFrame(matrix).T
        dfMatrix_gh = dfMatrix_gh.apply(pd.to_numeric, errors='coerce')
        dfMatrix_gh = dfMatrix_gh.replace([np.inf, -np.inf], 10000).fillna(0)
        dfMatrix_gh = dfMatrix_gh.round(0).astype(int)

        mask_connected = dfMatrix_gh.index.tolist()
    else:
        dfMatrix_gh = None

    if inputNameLanduse is not None:
        landuses = inputJson['input'][inputNameLanduse] 
        dfLanduses_gh = pd.DataFrame(landuses).T
        dfLanduses_gh = dfLanduses_gh.apply(pd.to_numeric, errors='coerce')
        dfLanduses_gh = dfLanduses_gh.replace([np.inf, -np.inf], 0).fillna(0)  # cleaning function?
        dfLanduses_gh = dfLanduses_gh.round(0).astype(int)
        
        if dfMatrix_gh is not None:
            valid_indexes = [idx for idx in mask_connected if idx in dfLanduses_gh.index]
            # Identify and report missing indexes
            missing_indexes = set(mask_connected) - set(valid_indexes)
            if missing_indexes:
                print(f"Error: The following indexes were not found in the DataFrame: {missing_indexes}, length: {len(missing_indexes)}")
        
            # Apply the filtered mask
            dfLanduses_gh = dfLanduses_gh.loc[valid_indexes]
        
    else:
        dfLanduses_gh = None
    
    
    if inputNameAttributeMapper is not None:
        attributeMapperDict_gh = inputJson['input'][inputNameAttributeMapper]
    else:
        attributeMapperDict_gh = None

    if inputNameLanduseMapper is not None:  
        landuseMapperDict_gh = inputJson['input'][inputNameLanduseMapper]
    else:
        landuseMapperDict_gh = None


    if inputNameAlpha is not None:
        alpha = inputJson['input'][inputNameAlpha]
        alpha = float(alpha)
        if alpha is None:
            alpha = alphaDefault
    else:
        alpha = alphaDefault
        
    if inputNameThreshold is not None:
        threshold = inputJson['input'][inputNameThreshold]
        threshold = float(threshold)
        if threshold is None:
            threshold = thresholdDefault
    else:
        threshold = thresholdDefault

    
    return dfMatrix_gh, dfLanduses_gh, attributeMapperDict_gh, landuseMapperDict_gh, alpha, threshold
                           


def splitDictByStrFragmentInColumnName(original_dict, substrings):
    result_dicts = {substring: {} for substring in substrings}
    for key, nested_dict in original_dict.items():
        for subkey, value in nested_dict.items():
            for substring in substrings:
                if substring in subkey:
                    if key not in result_dicts[substring]:
                        result_dicts[substring][key] = {}
                    result_dicts[substring][key][subkey] = value
    
    return result_dicts


def landusesToSubdomains(DistanceMatrix, LanduseDf, LanduseToSubdomainDict, UniqueSubdomainsList):
    df_LivabilitySubdomainsArea = pd.DataFrame(0, index=DistanceMatrix.index, columns=UniqueSubdomainsList)

    for subdomain in UniqueSubdomainsList:
        for lu, attributes in LanduseToSubdomainDict.items():
            if attributes["subdomain livability"] == subdomain:
                if lu in LanduseDf.columns:
                    if LanduseDf[lu].notna().any(): 
                        df_LivabilitySubdomainsArea[subdomain] = df_LivabilitySubdomainsArea[subdomain].add(LanduseDf[lu], fill_value=0)
                    else:
                        print(f"Warning: Column '{lu}' not found in landuse database")

    return df_LivabilitySubdomainsArea


def FindWorkplacesNumber (DistanceMatrix,livabilityMapperDict,destinationWeights,UniqueSubdomainsList ):
    
    df_LivabilitySubdomainsWorkplaces = pd.DataFrame(0, index=DistanceMatrix.index, columns=['jobs'])

    for subdomain in UniqueSubdomainsList:
        for key, values in livabilityMapperDict.items():
            if key and values['sqmPerEmpl']:
                sqm_per_empl = float(livabilityMapperDict[subdomain]['sqmPerEmpl'])  
                if key in destinationWeights.columns and key == subdomain:
                    if sqm_per_empl > 0:
                        df_LivabilitySubdomainsWorkplaces['jobs'] += (round(destinationWeights[key] / sqm_per_empl,2)).fillna(0)
                    else:
                        df_LivabilitySubdomainsWorkplaces['jobs'] += 0
            else:
                df_LivabilitySubdomainsWorkplaces['jobs'] += 0
    
    return df_LivabilitySubdomainsWorkplaces




def computeAccessibility (DistanceMatrix, destinationWeights=None,alpha = 0.0038, threshold = 600):
    
    decay_factors = np.exp(-alpha * DistanceMatrix) * (DistanceMatrix <= threshold)
    
    # for weighted accessibility (e. g. areas)
    if destinationWeights is not None: #not destinationWeights.empty:
        subdomainsAccessibility = pd.DataFrame(index=DistanceMatrix.index, columns=destinationWeights.columns)
        for col in destinationWeights.columns:
            subdomainsAccessibility[col] = (decay_factors * destinationWeights[col].values).sum(axis=1)
    else:
        print("Destination weights parameter is None")
    
    return subdomainsAccessibility



def computeAccessibility_pointOfInterest (DistanceMatrix, columnName, alpha = 0.0038, threshold = 600):
    
    decay_factors = np.exp(-alpha * DistanceMatrix) * (DistanceMatrix <= threshold)

    pointOfInterestAccessibility = pd.DataFrame(index=DistanceMatrix.index, columns=[columnName])
    for col in pointOfInterestAccessibility.columns:
        pointOfInterestAccessibility[col] = (decay_factors * 1).sum(axis=1)
    
    return pointOfInterestAccessibility



def remap(value, B_min, B_max, C_min, C_max):
    return C_min + (((value - B_min) / (B_max - B_min))* (C_max - C_min))    




def accessibilityToLivability (DistanceMatrix,accessibilityInputs, SubdomainAttributeDict,UniqueDomainsList):
    livability = pd.DataFrame(index=DistanceMatrix.index, columns=accessibilityInputs.columns)
                         
    for domain in UniqueDomainsList:
        livability[domain] = 0
        
    livability.fillna(0, inplace=True)
    templist = []
    # remap accessibility to livability points
    
    for key, values in SubdomainAttributeDict.items():
        threshold = float(SubdomainAttributeDict[key]['thresholds'])
        max_livability = float(SubdomainAttributeDict[key]['max_points'])
        domains = [str(item) for item in SubdomainAttributeDict[key]['domain']]
    
        if key in accessibilityInputs.columns and key != 'commercial':
            livability_score = remap(accessibilityInputs[key], 0, threshold, 0, max_livability)
            livability.loc[accessibilityInputs[key] >= threshold, key] = max_livability
            livability.loc[accessibilityInputs[key] < threshold, key] = livability_score          
            if any(domains):
                for domain in domains:
                    if domain != 'Workplaces':
                        livability.loc[accessibilityInputs[key] >= threshold, domain] += max_livability
                        livability.loc[accessibilityInputs[key] < threshold, domain] += livability_score
                                            
        elif key == 'commercial':
            livability_score = remap(accessibilityInputs['jobs'], 0, threshold, 0, max_livability)
            livability.loc[accessibilityInputs['jobs'] >= threshold, domains[0]] = max_livability
            livability.loc[accessibilityInputs['jobs'] < threshold, domains[0]] = livability_score

    
    return livability


def findUniqueDomains (livabilityMapperDict):
    # find a set of unique domains, to which subdomains are aggregated    
    temp = []
    domain_list = []
    for key, values in livabilityMapperDict.items():
        domain = livabilityMapperDict[key]['domain']
        for item in domain:
            if ',' in item:
                domain_list = item.split(',')
                livabilityMapperDict[key]['domain'] = domain_list
            for domain in domain_list:
                temp.append(domain) 
            else:
                if item != 0: 
                    temp.append(item)  
    
    domainsUnique = list(set(temp))
    return domainsUnique


def findUniqueSubdomains (landuseMapperDict):
    # find a list of unique subdomains, to which land uses are aggregated
    temp = []    
    for key, values in landuseMapperDict.items():
        subdomain = str(landuseMapperDict[key]["subdomain livability"])
        if subdomain != 0: 
            temp.append(subdomain) 
        
    subdomainsUnique = list(set(temp))
    return subdomainsUnique