I wanted to make a script in order to manage my friends and I, walkie talkies. so I created 4 classes: a Range is a modification that can upgrade the walkie talkie a Walkie Talkie describe the radio and its possible mods(Range) a Person describe me or my friends and their radios a TalkieRangeData is a class to handle all of this
Here is my code:
import json
import base64
import os
def is_base64(s):
"""Check if a parameter is encoded in base64."""
try:
base64.b64decode(s)
return True and s[-1] == "="
except:
return False
def to_base64(s):
"""Encode a parameter in base64 if it is not already encoded."""
if not is_base64(s):
s = base64.b64encode(s.encode()).decode()
return s
def from_base64(s):
"""Decode a parameter from base64 if it is encoded."""
if is_base64(s):
s = base64.b64decode(s).decode()
return s
class Range:
def __init__(self, mod, minrange, maxrange, commentary):
self.mod = mod
self.minrange = minrange
self.maxrange = maxrange
self.commentary = to_base64(commentary)
def update_range(
self, new_mod=None, new_min=None, new_max=None, new_commentary=None
):
if new_mod:
self.mod = new_mod
if new_min:
self.minrange = new_min
if new_max:
self.maxrange = new_max
if new_commentary:
self.commentary = to_base64(new_commentary)
def __repr__(self):
return f"Mod: {self.mod}\n\tMin: {self.minrange}\n\tMax: {self.maxrange}\n\tCommentary: {from_base64(self.commentary)} "
def toJSON(self):
return {
"mod": self.mod,
"minrange": self.minrange,
"maxrange": self.maxrange,
"commentary": self.commentary,
}
class WalkieTalkie:
def __init__(self, model, general_commentary, ranges=list()):
self.model = model
self.generalCommentary = to_base64(general_commentary)
self.ranges = list()
for r in ranges:
if isinstance(r, Range):
self.ranges.append(r)
else:
self.ranges.append(Range(*r))
def add_range(self, mod, minrange, maxrange, commentary):
self.ranges.append(Range(mod, minrange, maxrange, to_base64(commentary)))
def update_range(
self, mod, new_mod=None, new_min=None, new_max=None, new_commentary=None
):
range = next((r for r in self.ranges if r.mod == mod), None)
if range:
range.update_range(new_mod, new_min, new_max, new_commentary)
else:
raise ValueError("No range with that mod name was found.")
def delete_range(self, mod):
range = next((r for r in self.ranges if r.mod == mod), None)
if range:
self.ranges.remove(range)
else:
raise ValueError("No range with that mod name was found.")
def __repr__(self):
ranges_str = "\n".join([f"{range}" for range in self.ranges])
return f"Model: {self.model}\nGeneral Commentary: {from_base64(self.generalCommentary)}\nRanges:\n{ranges_str}"
def toJSON(self):
return {
"model": self.model,
"generalCommentary": self.generalCommentary,
"ranges": [r.toJSON() for r in self.ranges],
}
class Person:
def __init__(self, name, color, latitude, longitude, walkie_talkies=list()):
self.name = name
self.color = color
self.location = {"latitude": latitude, "longitude": longitude}
self.walkie_talkies = walkie_talkies
def add_walkie_talkie(self, model, general_commentary, ranges=list()):
wt = WalkieTalkie(model, general_commentary, ranges)
self.walkie_talkies.append(wt)
def add_walkie_talkie2(self, walkie_talkie: WalkieTalkie):
self.walkie_talkies.append(walkie_talkie)
def update_walkie_talkie(
self, model, new_model=None, new_general_commentary=None, new_ranges=None
):
wt_to_update = next(wt for wt in self.walkie_talkies if wt.model == model)
if new_model:
wt_to_update.model = new_model
if new_general_commentary:
wt_to_update.generalCommentary = to_base64(new_general_commentary)
if new_ranges:
wt_to_update.ranges = new_ranges
def delete_walkie_talkie(self, model):
self.walkie_talkies = [wt for wt in self.walkie_talkies if wt.model != model]
def __repr__(self):
walkie_talkies_str = "\n".join(
[f"{walkie_talkie}" for walkie_talkie in self.walkie_talkies]
)
return (
f"Name: {self.name}\nLocation: ({self.location['latitude']}, {self.location['longitude']})\nWalkie "
f"Talkies:\n{walkie_talkies_str} "
)
class TalkieRangeData:
def __init__(self, filepath):
self.filepath = filepath
self.people = self.read_data()
def read_data(self):
# if file path does not exist, create it
if not os.path.exists(self.filepath):
with open(self.filepath, "w") as f:
f.write('\{"people": []\}')
with open(self.filepath, "r") as f:
try:
data = json.load(f)
return [
Person(
person["name"],
person["color"],
person["location"]["latitude"],
person["location"]["longitude"],
[
WalkieTalkie(
wt["model"],
from_base64(wt["generalCommentary"]),
[
Range(
r["mod"],
r["minrange"],
r["maxrange"],
from_base64(r["commentary"]),
)
for r in wt["ranges"]
],
)
for wt in person["walkieTalkies"]
],
)
for person in data["people"]
]
except json.decoder.JSONDecodeError:
return list()
def add_person(self, name, color, latitude, longitude, overwrite=False):
if not overwrite:
existing_person = next(
(person for person in self.people if person.name == name), None
)
if existing_person:
raise ValueError(
"A person with that name already exists. Set `overwrite` to True if you want to update the existing person."
)
person = Person(name, color, latitude, longitude)
self.people.append(person)
self.update_json()
def add_person2(self, pperson: Person, overwrite=False):
if not overwrite:
existing_person = next(
(person for person in self.people if person.name == pperson.name), None
)
if existing_person:
raise ValueError(
"A person with that name already exists. Set `overwrite` to True if you want to update the existing person."
)
self.people.append(pperson)
self.update_json()
def update_json(self):
data = {
"people": [
{
"name": person.name,
"color": person.color,
"location": person.location,
"walkieTalkies": [wt.toJSON() for wt in person.walkie_talkies],
}
for person in self.people
]
}
with open(self.filepath, "w") as f:
json.dump(data, f, indent=2)
def update_person(
self,
name,
new_name=None,
new_latitude=None,
new_longitude=None,
new_walkie_talkies=None,
):
person_to_update = next(person for person in self.people if person.name == name)
if new_name:
person_to_update.name = new_name
if new_latitude:
person_to_update.location["latitude"] = new_latitude
if new_longitude:
person_to_update.location["longitude"] = new_longitude
if new_walkie_talkies:
person_to_update.walkie_talkies = new_walkie_talkies
self.update_json()
def delete_person(self, name):
self.people = [person for person in self.people if person.name != name]
self.update_json()
def __repr__(self):
return f"TalkieRangeData"
and here is my main.py file that I use to generate a kml file from input data
import json
import os
import simplekml
from TalkieRangeData import (
TalkieRangeData,
to_base64,
from_base64,
Person,
WalkieTalkie,
Range,
)
from polycircles import polycircles
def load_data():
if os.path.exists("talkies.json"):
trd = TalkieRangeData("talkies.json")
else:
print(
"talkies.json does not exist. Let's create it.First we need to create at least one user."
)
name = input("Enter the name of the person: ")
color = input("Enter the color of the person: ")
latitude = input("Enter the latitude of the person: ")
longitude = input("Enter the longitude of the person: ")
model = input("Enter the model of the walkie talkie: ")
general_commentary = input(
"Enter the general commentary of the walkie talkie: "
)
mod = input("Enter the mod of the range: ")
rangemin = input("Enter the min of the range: ")
rangemax = input("Enter the max of the range: ")
commentary = input("Enter the commentary of the range: ")
json_data = {
"people": [
{
"name": name,
"location": {"latitude": latitude, "longitude": longitude},
"walkieTalkies": [
{
"model": model,
"generalCommentary": to_base64(general_commentary),
"ranges": [
{
"mod": mod,
"min": rangemin,
"max": rangemax,
"commentary": to_base64(commentary),
}
],
}
],
}
]
}
with open("talkies.json", "w") as f:
json.dump(json_data, f)
trd = TalkieRangeData("talkies.json")
return trd
def main():
j = Person("J", "00ff00", 0.956131, 0.587348)
t = Person("T", "ff0080", 0.943916, 0.609007)
a = Person("A", "ff00ff", 0.961872, 0.596557)
l = Person("L", "0000ff", 0.942723, 0.740706)
bf888s = WalkieTalkie(
"BF-888S",
"This is a BF-888S",
[Range("antenne normal", 1000, 2000, "antenne standard")],
)
uv5r = WalkieTalkie(
"UV-5R",
"This is a UV-55R",
[Range("antenne normal", 5000, 7000, "antenne standard")],
)
thuv88 = WalkieTalkie(
"TH-UV88",
"This is a TH-UV88",
[
Range("antenne normal", 3000, 5000, "antenne standard"),
Range("antenne longue", 6000, 13000, "antenne longue"),
],
)
# we all have a BF-888S
j.add_walkie_talkie2(bf888s)
t.add_walkie_talkie2(bf888s)
a.add_walkie_talkie2(bf888s)
l.add_walkie_talkie2(bf888s)
j.add_walkie_talkie2(uv5r)
l.add_walkie_talkie2(thuv88)
# trd = load_data()
trd = TalkieRangeData("talkies.json")
trd.add_person2(j, overwrite=True)
trd.add_person2(t, overwrite=True)
trd.add_person2(a, overwrite=True)
trd.add_person2(l, overwrite=True)
kml = simplekml.Kml()
# Create a folder for each person
for person in trd.people:
folder = kml.newfolder(name=person.name)
for walkie_talkie in person.walkie_talkies:
# Create a folder for each walkie talkie
wt_folder = folder.newfolder(
name=walkie_talkie.model,
description=from_base64(walkie_talkie.generalCommentary),
)
for mod in walkie_talkie.ranges:
# Create a folder for each mod
mod_folder = wt_folder.newfolder(
name=mod.mod, description=from_base64(mod.commentary)
)
# create two series of points for the ranges
polygon = polycircles.Polycircle(
latitude=person.location["latitude"],
longitude=person.location["longitude"],
radius=mod.minrange,
number_of_vertices=100,
)
polygon2 = polycircles.Polycircle(
latitude=person.location["latitude"],
longitude=person.location["longitude"],
radius=mod.maxrange,
number_of_vertices=100,
)
# add the points to the kml using polygons in order
bottomcircle = mod_folder.newpolygon(
name="{}-{}-{} max range".format(
person.name, walkie_talkie.model, mod.mod
),
outerboundaryis=polygon2.to_kml(),
)
topcircle = mod_folder.newpolygon(
name="{}-{}-{} min range".format(
person.name, walkie_talkie.model, mod.mod
),
outerboundaryis=polygon.to_kml(),
)
# set the color of the polygons
bottomcircle.style.labelstyle.scale = 1
bottomcircle.style.labelstyle.color = simplekml.Color.hexa(
person.color + "33"
)
bottomcircle.style.polystyle.color = simplekml.Color.hexa(
person.color + "33"
)
topcircle.style.labelstyle.scale = 1
topcircle.style.labelstyle.color = simplekml.Color.hexa(
person.color + "80"
)
topcircle.style.polystyle.color = simplekml.Color.hexa(
person.color + "80"
)
kml.save("talkies.kml")
if __name__ == "__main__":
main()
I'm very new to python oop and here is the issue I fall onto. My code create a json file the format seems to be correct but it gave all my Person the same talkies so it look like this:
- j
- bf888s
- bf888s
- bf888s
- bf888s
- uv5r
- thuv88
- l
- bf888s
- bf888s
- bf888s
- bf888s
- uv5r
- thuv88
- a
- bf888s
- bf888s
- bf888s
- bf888s
- uv5r
- thuv88
- t
- bf888s
- bf888s
- bf888s
- bf888s
- uv5r
- thuv88
By running in debug mod I found out that each time I call add_walkie_talkie2(wt) it appends the wt to all my Person, does anyone knows why this occurs ?