Added serverside modeling

This commit is contained in:
Computerboer
2023-06-16 00:53:23 +02:00
parent 53b7d14b9e
commit 78afe97442
9 changed files with 23197 additions and 21 deletions

View File

@@ -28,14 +28,26 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Compile Include="app.py" /> <Compile Include="app.py" />
<Compile Include="config.py" /> <Compile Include="utils\auctionutils.py" />
<Compile Include="cache.py" />
<Compile Include="utils\locationutils.py" />
<Compile Include="models\auction.py" />
<Compile Include="models\location.py" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<InterpreterReference Include="Global|PythonCore|3.6-32" /> <InterpreterReference Include="Global|PythonCore|3.6-32" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Content Include="data\locationfiles\NL.txt" />
<Content Include="data\locationfiles\readme.txt" />
<Content Include="requirements.txt" /> <Content Include="requirements.txt" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Folder Include="data\locationfiles\" />
<Folder Include="models\" />
<Folder Include="data\" />
<Folder Include="utils\" />
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Python Tools\Microsoft.PythonTools.targets" /> <Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Python Tools\Microsoft.PythonTools.targets" />
<!-- Uncomment the CoreCompile target to enable the Build command in <!-- Uncomment the CoreCompile target to enable the Build command in
Visual Studio and specify your pre- and post-build commands in Visual Studio and specify your pre- and post-build commands in

31
app.py
View File

@@ -1,7 +1,12 @@
from traceback import print_exc
from flask import Flask, jsonify from flask import Flask, jsonify
from flask_cors import CORS, cross_origin from flask_cors import CORS, cross_origin
import requests import requests
from config import Cache from cache import Cache
from utils.auctionutils import getTwkAuctions, getAuctionlocations
from utils.locationutils import getGeoLocationByCity
from models.location import JsonEncoder
import json
app = Flask(__name__) app = Flask(__name__)
CORS(app, resources={r"/auction/*": {"origins": ["http://localhost:4200","https://auctionviewer.ikbenhenk.nl", "https://victorious-bay-0d278c903.3.azurestaticapps.net"]}}) CORS(app, resources={r"/auction/*": {"origins": ["http://localhost:4200","https://auctionviewer.ikbenhenk.nl", "https://victorious-bay-0d278c903.3.azurestaticapps.net"]}})
@@ -14,22 +19,20 @@ def gethome():
@app.route("/auction/<countrycode>") @app.route("/auction/<countrycode>")
def get(countrycode): def get(countrycode):
try:
if countrycode not in ['NL', 'BE', 'DE']:
print(f'country not available: {countrycode} ')
return jsonify('NOT AVAILABLE COUNTRY')
if countrycode not in ['NL', 'BE', 'DE']:
print(f'country not available: {countrycode} ')
return jsonify('NOT AVAILABLE COUNTRY')
res = Cache.get(countrycode) res = getAuctionlocations(countrycode)
if(res): #return json.dumps(res, sort_keys=True, default=str)
return res.obj return JsonEncoder().encode(res)
response = requests.get("https://api.troostwijkauctions.com/sale/4/listgrouped?batchSize=99999&CountryIDs=" + countrycode) except Exception as e:
print(f'request statuscode: {response.status_code} ') print('something went wrong ')
print_exc(e)
if(response.status_code ==200): return 'internal server error', 500
Cache.add(countrycode, response.json())
return response.json();
if __name__ == "__main__": if __name__ == "__main__":
app.run() # run our Flask app app.run() # run our Flask app

View File

@@ -3,16 +3,18 @@ from datetime import datetime, timedelta
cache = {} cache = {}
class Cache(): class Cache():
def get(key): def get(key, notOlderThanHours = 24):
print('get key ' + key) #print('get key ' + key)
if key in cache: if key in cache:
cacheobj = cache[key] cacheobj = cache[key]
if(cacheobj.isOlderThanHours(24)): if(not cache):
return None
if(cacheobj.isOlderThanHours(notOlderThanHours)):
print('removing cacheobject ' + key) print('removing cacheobject ' + key)
del cache[key] del cache[key]
return None return None
print('returning cacheobject ' + key) #print('returning cacheobject ' + key)
return cache[key] return cacheobj.obj
def add(key, obj): def add(key, obj):
print('adding cacheobject ' + key) print('adding cacheobject ' + key)
@@ -27,6 +29,6 @@ class CacheObj:
self.time=datetime.now() self.time=datetime.now()
def isOlderThanHours(self, hours): def isOlderThanHours(self, hours):
print(f'checking time cacheobject {self.time} < {datetime.now() - timedelta(hours=hours)}') #print(f'checking time cacheobject {self.time} < {datetime.now() - timedelta(hours=hours)}')
return self.time < datetime.now() - timedelta(hours=hours) return self.time < datetime.now() - timedelta(hours=hours)

22813
data/locationfiles/NL.txt Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,141 @@
Readme for GeoNames Gazetteer extract files
============================================================================================================
This work is licensed under a Creative Commons Attribution 4.0 License,
see https://creativecommons.org/licenses/by/4.0/
The Data is provided "as is" without warranty or any representation of accuracy, timeliness or completeness.
The data format is tab-delimited text in utf8 encoding.
Files :
-------
XX.zip : features for country with iso code XX, see 'geoname' table for columns. 'no-country' for features not belonging to a country.
allCountries.zip : all countries combined in one file, see 'geoname' table for columns
cities500.zip : all cities with a population > 500 or seats of adm div down to PPLA4 (ca 185.000), see 'geoname' table for columns
cities1000.zip : all cities with a population > 1000 or seats of adm div down to PPLA3 (ca 130.000), see 'geoname' table for columns
cities5000.zip : all cities with a population > 5000 or PPLA (ca 50.000), see 'geoname' table for columns
cities15000.zip : all cities with a population > 15000 or capitals (ca 25.000), see 'geoname' table for columns
alternateNamesV2.zip : alternate names with language codes and geonameId, file with iso language codes, with new columns from and to
alternateNames.zip : obsolete use V2, this file does not have the new columns to and from and will be removed in the future
admin1CodesASCII.txt : names in English for admin divisions. Columns: code, name, name ascii, geonameid
admin2Codes.txt : names for administrative subdivision 'admin2 code' (UTF8), Format : concatenated codes <tab>name <tab> asciiname <tab> geonameId
iso-languagecodes.txt : iso 639 language codes, as used for alternate names in file alternateNames.zip
featureCodes.txt : name and description for feature classes and feature codes
timeZones.txt : countryCode, timezoneId, gmt offset on 1st of January, dst offset to gmt on 1st of July (of the current year), rawOffset without DST
countryInfo.txt : country information : iso codes, fips codes, languages, capital ,...
see the geonames webservices for additional country information,
bounding box : http://api.geonames.org/countryInfo?
country names in different languages : http:/api.geonames.org/countryInfoCSV?lang=it
modifications-<date>.txt : all records modified on the previous day, the date is in yyyy-MM-dd format. You can use this file to daily synchronize your own geonames database.
deletes-<date>.txt : all records deleted on the previous day, format : geonameId <tab> name <tab> comment.
alternateNamesModifications-<date>.txt : all alternate names modified on the previous day,
alternateNamesDeletes-<date>.txt : all alternate names deleted on the previous day, format : alternateNameId <tab> geonameId <tab> name <tab> comment.
userTags.zip : user tags , format : geonameId <tab> tag.
hierarchy.zip : parentId, childId, type. The type 'ADM' stands for the admin hierarchy modeled by the admin1-4 codes. The other entries are entered with the user interface. The relation toponym-adm hierarchy is not included in the file, it can instead be built from the admincodes of the toponym.
adminCode5.zip : the new adm5 column is not yet exported in the other files (in order to not break import scripts). Instead it is availabe as separate file.
columns: geonameId,adm5code
The main 'geoname' table has the following fields :
---------------------------------------------------
geonameid : integer id of record in geonames database
name : name of geographical point (utf8) varchar(200)
asciiname : name of geographical point in plain ascii characters, varchar(200)
alternatenames : alternatenames, comma separated, ascii names automatically transliterated, convenience attribute from alternatename table, varchar(10000)
latitude : latitude in decimal degrees (wgs84)
longitude : longitude in decimal degrees (wgs84)
feature class : see http://www.geonames.org/export/codes.html, char(1)
feature code : see http://www.geonames.org/export/codes.html, varchar(10)
country code : ISO-3166 2-letter country code, 2 characters
cc2 : alternate country codes, comma separated, ISO-3166 2-letter country code, 200 characters
admin1 code : fipscode (subject to change to iso code), see exceptions below, see file admin1Codes.txt for display names of this code; varchar(20)
admin2 code : code for the second administrative division, a county in the US, see file admin2Codes.txt; varchar(80)
admin3 code : code for third level administrative division, varchar(20)
admin4 code : code for fourth level administrative division, varchar(20)
population : bigint (8 byte int)
elevation : in meters, integer
dem : digital elevation model, srtm3 or gtopo30, average elevation of 3''x3'' (ca 90mx90m) or 30''x30'' (ca 900mx900m) area in meters, integer. srtm processed by cgiar/ciat.
timezone : the iana timezone id (see file timeZone.txt) varchar(40)
modification date : date of last modification in yyyy-MM-dd format
AdminCodes:
Most adm1 are FIPS codes. ISO codes are used for US, CH, BE and ME. UK and Greece are using an additional level between country and fips code. The code '00' stands for general features where no specific adm1 code is defined.
The corresponding admin feature is found with the same countrycode and adminX codes and the respective feature code ADMx.
The table 'alternate names' :
-----------------------------
alternateNameId : the id of this alternate name, int
geonameid : geonameId referring to id in table 'geoname', int
isolanguage : iso 639 language code 2- or 3-characters; 4-characters 'post' for postal codes and 'iata','icao' and faac for airport codes, fr_1793 for French Revolution names, abbr for abbreviation, link to a website (mostly to wikipedia), wkdt for the wikidataid, varchar(7)
alternate name : alternate name or name variant, varchar(400)
isPreferredName : '1', if this alternate name is an official/preferred name
isShortName : '1', if this is a short name like 'California' for 'State of California'
isColloquial : '1', if this alternate name is a colloquial or slang term. Example: 'Big Apple' for 'New York'.
isHistoric : '1', if this alternate name is historic and was used in the past. Example 'Bombay' for 'Mumbai'.
from : from period when the name was used
to : to period when the name was used
Remark : the field 'alternatenames' in the table 'geoname' is a short version of the 'alternatenames' table without links and postal codes but with ascii transliterations. You probably don't need both.
If you don't need to know the language of a name variant, the field 'alternatenames' will be sufficient. If you need to know the language
of a name variant, then you will need to load the table 'alternatenames' and you can drop the column in the geoname table.
Boundaries:
Simplified country boundaries are available in two slightly different formats:
shapes_simplified_low:
geonameId: The geonameId of the feature
geoJson: The boundary in geoJson format
shapes_simplified_low.json:
similar to the abovementioned file, but fully in geojson format. The geonameId is a feature property in the geojson string.
Statistics on the number of features per country and the feature class and code distributions : http://www.geonames.org/statistics/
Continent codes :
AF : Africa geonameId=6255146
AS : Asia geonameId=6255147
EU : Europe geonameId=6255148
NA : North America geonameId=6255149
OC : Oceania geonameId=6255151
SA : South America geonameId=6255150
AN : Antarctica geonameId=6255152
feature classes:
A: country, state, region,...
H: stream, lake, ...
L: parks,area, ...
P: city, village,...
R: road, railroad
S: spot, building, farm
T: mountain,hill,rock,...
U: undersea
V: forest,heath,...
If you find errors or miss important places, please do use the wiki-style edit interface on our website
http://www.geonames.org to correct inaccuracies and to add new records.
Thanks in the name of the geonames community for your valuable contribution.
Data Sources:
http://www.geonames.org/data-sources.html
More Information is also available in the geonames faq :
http://forum.geonames.org/gforum/forums/show/6.page
The forum : http://forum.geonames.org
or the google group : http://groups.google.com/group/geonames

0
models/auction.py Normal file
View File

51
models/location.py Normal file
View File

@@ -0,0 +1,51 @@
from enum import Enum
import numbers
import string
import json
from json import JSONEncoder
class Countrycode(Enum):
NL = "NL",
DE = "DE",
BE = "BE"
class Auctionbrand(str, Enum):
NONE = "NONE",
TWK = "TWK"
class GeonameLocation:
def __init__(self, geonameid = 0, name = "", asciiname = "", alternatenames = [], latitude = 0, longitude = 0, countrycode:Countrycode = Countrycode.NL, modificationdate = "") :
self.geonameid = geonameid
self.name = name
self.asciiname = asciiname
self.alternatenames = alternatenames
self.latitude = latitude
self.longitude = longitude
self.countrycode = countrycode
self.modificationdate = modificationdate
class Maplocation:
def __init__(self, lat = 0, long = 0, numberofauctions = 0, geonamelocation:GeonameLocation = None, auctions = []):
self.lat = lat
self.long = long
self.numberofauctions = numberofauctions
self.geonamelocation = geonamelocation
self.auctions = auctions
class Auction:
def __init__(self, auctionbrand: Auctionbrand = Auctionbrand.NONE, city = "", countrycode:Countrycode = Countrycode.NL, name = "", starttime = None, closingtime = None, url = "", imageurl = "", numberoflots = 0, geonamelocation: GeonameLocation = None):
self.city = city
self.countrycode = countrycode
self.name = name
self.starttime = str(starttime)
self.closingtime = str(closingtime)
self.url = url
self.imageurl = imageurl
self.numberoflots = numberoflots
self.geonamelocation = geonamelocation
self.brand = auctionbrand
class JsonEncoder(JSONEncoder):
def default(self, o):
return o.__dict__

66
utils/auctionutils.py Normal file
View File

@@ -0,0 +1,66 @@
import requests
from cache import Cache
from models.location import Auction, Auctionbrand, Countrycode, Maplocation
from utils.locationutils import getGeoLocationByCity
from datetime import datetime
def getAuctionlocations(countrycode: Countrycode):
cachename = 'allauctions_' + countrycode
res = Cache.get(cachename)
if(res): return res
auctions = getTwkAuctions(countrycode)
for auction in auctions:
auction.geonamelocation = getGeoLocationByCity(auction.city, countrycode)
geonameids = map(get_geonameid, auctions)
uniquegeonameids = (list(set(geonameids)))
maplocations = []
#loops through the uniques geonameids
for geoid in uniquegeonameids:
#filters all auctions for this geonameid
geoauctions = list(filter(lambda a: get_geonameid(a) == geoid , auctions))
if(geoauctions):
#gets the location (if it has any) for the geolocation
location = geoauctions[0].geonamelocation
if(location):
maplocation = Maplocation(location.latitude, location.longitude, len(geoauctions), location, geoauctions)
maplocations.append(maplocation);
for location in maplocations:
del location.geonamelocation #removes object which is not used anymore
for auction in location.auctions:
del auction.geonamelocation #removes object to not have duplicate data send to the server
Cache.add(cachename, maplocations)
return maplocations
def get_geonameid(auction):
if(auction.geonamelocation):
return auction.geonamelocation.geonameid
return None
def getTwkAuctions(countrycode):
cachename = 'TwkAuctions_'+ countrycode
response = requests.get("https://api.troostwijkauctions.com/sale/4/listgrouped?batchSize=99999&CountryIDs=" + countrycode)
res = Cache.get(cachename)
if(res):return res
if(response.status_code ==200):
print('Got Twk Auctions')
data = response.json();
auctions = []
for result in data['results']:
for twka in result['items']:
a = Auction(Auctionbrand.TWK, twka['c'], twka['cc'], twka['n'], datetime.fromtimestamp(twka['sd']), datetime.fromtimestamp(twka['cd']), twka['url'], twka['ii'], twka['nol'] )
auctions.append(a)
Cache.add(cachename, auctions)
return auctions
return None

88
utils/locationutils.py Normal file
View File

@@ -0,0 +1,88 @@
import re
import os
from pathlib import Path
from cache import Cache
from models.location import Countrycode, GeonameLocation
def getLocationArray(countrycode: Countrycode):
cachename = 'locations_' + countrycode
res = Cache.get(cachename,672) #a month in the cache is long enough...
if(res):return res
base_dir = Path(os.path.dirname(__file__)).parent.absolute() #<-- absolute dir the script is in
#filename = "\data\locationfiles\\" + countrycode + ".txt"
abs_file_path = os.path.join(base_dir, "data", "locationfiles", countrycode + ".txt")
with open(abs_file_path, encoding='utf-8', errors='ignore') as f:
#datalines = f.readlines();
geonames = []
for line in f:
line = line.rstrip('\n');
data = line.split("\t")
alternatenames = []
if data[3] != "":
alternatenames = data[3].lower().split(",")
geoname = GeonameLocation(data[0], data[1].lower(), data[2].lower(), alternatenames, data[4], data[5], data[8], data[18])
geonames.append(geoname)
Cache.add(cachename,geonames)
return geonames
def getGeoLocationByCity(city = "", countrycode: Countrycode = Countrycode.NL ):
city = city.lower();
cityname = city
if(not "gemeente" in cityname):
cityname = "gemeente "+ cityname
geonames = getLocationArray(countrycode)
#first tries name with 'gemeente as prefix'
geo = list(filter(lambda g: g.name == cityname, geonames))
if(geo): geo = geo[0]
#print('first try' + repr(geo))
if (geo): return geo;
#also tries in the alternatenames
geo = list(filter(lambda g: inAlternatenames(g.alternatenames, cityname), geonames))
if(geo): geo = geo[0]
#print('alternatenames'+ repr( geo))
if (geo): return geo;
#then tries name without 'gemeente as prefix'
geo = list(filter(lambda g: g.name == city, geonames))
if(geo): geo = geo[0]
#print('without gemeente' + repr( geo))
if (geo): return geo;
#also tries in the alternatenames
geo = list(filter(lambda g: inAlternatenames(g.alternatenames, city), geonames))
if(geo): geo = geo[0]
#print('alternatenames without gemeente' + repr( geo))
if (geo): return geo;
#removes everything between () and then removes the leading and trailing spaces;
print('name before regex ' + city)
#city = re.sub('/\([^()]*\)/g', '', city)
city = re.sub("[\(].*?[\)]", "", city)
city = city.strip();
print('name after regex ' + city)
geo = list(filter(lambda g: g.name == city, geonames))
if(geo): geo = geo[0]
#print('without anything between ()' + repr( geo))
if (geo): return geo;
#also tries in the alternatenames
geo = list(filter(lambda g: inAlternatenames(g.alternatenames, city), geonames))
if(geo): geo = geo[0]
#print('alternatenames without ()'+ repr( geo))
if (geo): return geo;
print('city not found: '+ city)
return None;
def inAlternatenames(alternatenames = [], name = ""):
return name in alternatenames