File size: 12,936 Bytes
eda0b1c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4508e72
 
eda0b1c
c5b678e
f91b75c
c5b678e
eda0b1c
c5b678e
 
 
 
 
 
eda0b1c
 
e07f8da
 
cbbdd13
eda0b1c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0a00112
962d621
 
 
77e01dc
962d621
f07b26f
0a00112
 
77e01dc
0a00112
77e01dc
 
 
 
 
 
 
962d621
 
 
 
 
f07b26f
962d621
 
 
 
f07b26f
0a00112
 
 
 
 
962d621
 
0a00112
962d621
 
 
 
 
 
 
 
 
 
 
 
 
 
ea9bc33
034c561
962d621
 
a4cf13e
962d621
 
 
 
 
 
 
034c561
 
962d621
034c561
962d621
 
 
 
 
 
 
 
eda0b1c
 
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
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

notionToken = os.getenv('notionToken')
notion = Client(auth=notionToken)
speckleToken = os.getenv('speckleToken')

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


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



# 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 fetchDomainMapper (luAttributePages):
        
    lu_domain_mapper ={}
    #subdomains_unique = []
    
    for page in luAttributePages:
        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:
            lu_domain_mapper[value_landuse] = {
            'subdomain livability': value_subdomain,
            'is origin': origin
            }
            #lu_domain_mapper[value_landuse] = value_subdomain
        #if value_subdomain != "":
            #subdomains_unique.append(value_subdomain)
       
    #subdomains_unique = list(set(subdomains_unique))
    return lu_domain_mapper


def fetchSubdomainMapper (livabilityAttributePages):

    attribute_mapper ={}
    domains_unique = []
    
    for page in livabilityAttributePages:
        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:   
            attribute_mapper[subdomain] = {
            'sqmPerEmpl': sqm_per_employee if sqm_per_employee != "" else 0,
            'thresholds': thresholds,
            'max_points': max_points,
            'domain': [domain if domain != "" else 0]
            }    
        if domain != "":
            domains_unique.append(domain)
    
    #domains_unique = list(set(domains_unique))
    return attribute_mapper



                                      
def fetchDistanceMatrices (stream_distance_matrices):

    #nested_list = []
    # navigate to list with speckle objects of interest
    distance_matrices = {}
    for distM in stream_distance_matrices["@Data"]['@{0}']:
      for kk in distM.__dict__.keys():
        try:
          if kk.split("+")[1].startswith("distance_matrix"):
            distance_matrix_dict = json.loads(distM[kk])
            origin_ids = distance_matrix_dict["origin_uuid"]
            destination_ids = distance_matrix_dict["destination_uuid"]
            distance_matrix =  distance_matrix_dict["matrix"]
            #chunks = distance_matrix_dict["chunks"]
            
            # Convert the distance matrix to a DataFrame
            df_distances = pd.DataFrame(distance_matrix, index=origin_ids, columns=destination_ids)
    
            # i want to add the index & colum names to dist_m_csv
            #distance_matrices[kk]  = dist_m_csv[kk]
            distance_matrices[kk] = df_distances
        except:
          pass

    return distance_matrices




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