Head of SEO & web analytics, Tensor (developer of saby.ru), author of the Telegram channel «VladPRO»
I have been working in digital marketing analytics, SEO, and team management for more than 10 years. And in myTelegram channel and on the website, I write even more about various aspects of real marketing. I consult on web project analytics and marketing tool setup, helping conduct technical interviews for traffic specialists.
ChizhikChatGPT Codex
A home variant of artificial intelligence. Lives in the terminal, feeds on logs and tokens. The name comes from the consonants of ChatGPT Codex — Ch‑Zh‑K. With a bit of imagination, it becomes Chizhik. With a lot of imagination, it's an engineer who can connect to any network and code something.
Vlad:
I need a small desktop educational app. On the image, there's a list of objects. The child should be able to point to each of them on the map. If they point incorrectly, a short hint should pop up: where the object is located and how to remember its position more easily. It should work specifically with the world map.
Chizhik:
Understood. So, we're making not an encyclopedia and not a four-button quiz, but a geography game. Only targets are not shown in advance, but after a miss, we don't shout "wrong," but explain where to look.
Thus began "Кругосветка" — a small offline application for Windows, which eventually grew to 82 learning objects, a detailed vector map, and a normal installer.
What exactly was to be built
Requirements were initially placed in a few lines:
The world map should always be on the screen.
Geographic names cannot be shown on the map: otherwise, it would become an exercise "find the already labeled answer."
The program names an object, and the child points it out with the mouse.
After an error, the application shows the correct position, a short explanation, and a mnemonic.
Internet is not needed for operation.
Launch is via a regular Windows shortcut, without a terminal and without pre-installing Python.
The last point appeared a bit later. As is usually the case, the first version of the technical specification ended with the word "application," while the current one ends with the words "to install on any computer." First, we had to figure out the photo.
From a crumpled sheet to data structure.
On the photo, there were 84 lines. Two objects — the Red Sea and the Baltic Sea — met twice, so after removing duplicates, there remained 82 unique names:
11 rivers;
3 currents;
10 islands and archipelagos;
9 peninsulas;
9 bays;
14 seas;
18 lakes;
6 straits;
Great Barrier Reef;
Mariana Trench.
Simply saving the names was not enough. For each object, a category, coordinates, acceptable tolerance, explanation, mnemonic, and a small fact were needed. One element of the set looks approximately like this:
dict( name="Volga", category="Rivers", anchors=[(57.0, 33.0), (55.5, 42.0), (48.5, 46.5)], tolerance=650, location=( "In the European part of Russia: flows southeast " "and empties into the Caspian Sea." ), mnemonic=( "Volga — Russian road to the Caspian: " "look for it between Moscow and the Caspian Sea." ), fact="The longest river in Europe.", )Such a file turned out to be longer than the main logic of some home projects. It contains 82 small geography cards, and each should be accurate enough for verification while still simple enough for a child.
Mnemonics had to be written not in the spirit of a reference book, but through the object's form and its neighbors. Lake Baikal is "a narrow crescent in Southern Siberia", Madagascar is "a large elongated island to the right of Africa", and the Mississippi is "a vertical seam in the middle of the USA". These are hooks that memory can catch to locate the object on a map.
Why one coordinate is not enough
For Titicaca or Baskunchak, one point is almost enough. With the Amazon, it won't work. The river stretches across a continent. If you check the distance only to a conditional "center" of the object, a child might hit the mouth of the Amazon and still get an error. Therefore, elongated objects are described by several anchor coordinates.
Between neighboring points, the program adds intermediate samples, and then calculates the minimum distance from the click to the resulting line. For distance on the surface of the Earth, the haversine formula is used:
def distance_to_object(lat, lon, anchors): samples = list(anchors) for (lat1, lon1), (lat2, lon2) in zip(anchors, anchors[1:]): delta_lon = ((lon2 - lon1 + 180) % 360) - 180 steps = max(2, int(max(abs(lat2 - lat1), abs(delta_lon)) / 1.5)) for step in range(1, steps): t = step / steps sample_lon = ((lon1 + delta_lon * t + 180) % 360) - 180 samples.append((lat1 + (lat2 - lat1) * t, sample_lon)) return min( haversine_km(lat, lon, target_lat, target_lon) for target_lat, target_lon in samples )Each object has its own tolerance in kilometers. A small lake cannot be assigned the error of an ocean current, and it is unreasonable to demand precision down to the nearest schoolyard when clicking on Greenland. The tolerance here sets the pedagogically reasonable area of response on the world map.
The map, which was initially too simple
For the shell we chose Python and Tkinter. The application is small, it should run locally and not require a browser. The map is drawn by Matplotlib, embedded in the window through FigureCanvasTkAgg. This combination doesn't look as modern as all JavaScript frameworks at once, but it runs as a regular desktop program and handles the mouse well.
The first map formally fulfilled the task: continents were in place, oceans too, you could zoom in and answer. I looked at it and quickly returned Chizhik from the state of premature victory. Here is the first version.

Vlad:
No, it's still a bit weak. The map doesn't include the Caspian Sea, Baskunchak, the Great Lakes, and rivers. Plot everything accurately, but don't label them. The child should see the objects, not their names.
This comment changed the cartographic base. For such a trainer, the continent outlines are insufficient. If the list includes Lake Ladoga, Onega, Baskunchak, Elton, the Great Lakes, Volga, and Angara, they should physically be present on the map before the question starts.
As a base, we used Natural Earth — an open set of coordinated vector layers. Main physical data were used at a scale of 1:10 million:
land contours;
water bodies and inland seas;
rivers and lake axial lines;
coral reefs;
country boundaries as non-intrusive references.
In total, the original GeoJSON layers take up about 26 MB. They are located next to the program, so the map loads without network tiles, API key, and internet connection.
There was a separate cartographic detail with the Caspian Sea. It is represented as an internal hole in the polygon layer. If we draw only the external contours of the continents and ignore the internal rings, the Caspian Sea would really disappear under the color of land. We had to extract these rings separately and fill them with the color of water:
land_patches, inland_seas = load_polygon_patches( LAND_FILE, include_holes=True, ) self.ax.add_collection(PatchCollection(land_patches, ...)) self.ax.add_collection( PatchCollection( inland_seas, facecolor="#9ED5EC", edgecolor="#3F89AA", ) )The lakes from the task received slightly more contrasting contours, and the necessary rivers ‑ thicker lines. Names are not displayed at this time. The attribute name_en is needed by the program only for selecting geometry, the child does not see it.
In the end, it turned out to be something like this:

Not every line on the map comes from GeoJSON
Land, rivers, and lakes fit well into the ready datasets. It's more complicated with ocean currents and the Mariana Trench. For the educational map, we didn't need an oceanographic model; recognizable landmarks were needed. Currents are drawn as dashed lines with arrows based on reference coordinates. The Mariana Trench is shown as a long line in the western part of the Pacific Ocean, and the Great Barrier Reef is taken from a separate reef layer. During the question, these objects are not labeled, but after the answer, the program can highlight the relevant area. When dragging and answering use one mouse button, it turned out convenient to zoom in with the mouse wheel. Moving the map was not convenient. Vlad: Make it possible to move the map while holding the button down. And if the user accidentally clicks, let a confirmation pop up: "Is this your final answer?" — YES, NO. This request has a small conflict. The left mouse button should simultaneously mean "start dragging the map" and "select the answer." You can distinguish one from the other only after moving or releasing the button. Therefore, the processing is split into three events: button_press_event → remember the point and current map boundaries motion_notify_event → after a threshold of 5 pixels, consider it a dragging gesture button_release_event → if there was no movement, consider it as the answer. During dragging, the longitude and latitude limits shift proportionally to the cursor movement, but they do not allow dragging the Earth completely out of the window. Scaling occurs relative to the cursor position, so you can hover the mouse over Lake Baikal and zoom in on Siberia specifically, rather than always zooming in on the center of the Atlantic.
A short click does not check anything. It places an orange marker and shows the question:
Is this your final answer?
"NO" removes the marker. "YES" starts the distance calculation. This additional step slightly slows down the test, but prevents accidental touches after moving the map from becoming errors and messing up the statistics.

In code, it looks like this:
def on_map_click(self, lat, lon): self.pending_click = (lat, lon) self.world_map.mark_candidate(lat, lon) self.confirm_frame.pack(fill="x") def confirm_answer(self): lat, lon = self.pending_click distance = distance_to_object(lat, lon, self.current["anchors"]) correct = distance <= self.current["tolerance"] ```But it is exactly these small details that distinguish a working prototype from a program that is not only convenient for its author but also for others.
What happens after an error
We have made two modes. In "Training" mode, you can request a hint. After an incorrect answer, the map shows the object, and three short blocks appear nearby:
where it is located;
how to remember;
interesting fact.
The incorrect object is returned once closer to the end of the queue. Not immediately, when the answer is still in your eyes, but later. This way, the program checks whether the explanation remains in memory.
In "Test" mode, there are no hints. The result is shown after completing the selected set. You can train on all 82 objects or choose a category: only rivers, seas, lakes, straits, and so on.
Statistics are stored locally in a regular JSON file in %LOCALAPPDATA%. No registration, cloud profile, or synchronization with the server. For a home trainer, this is not a limitation, but a comfort: the application opens even when the internet decided to check geography knowledge and went somewhere unknown.
"Make me a shortcut" turned out to be an intermediate requirement.
On the developer's computer, everything was run through Python. The user doesn't get any heat or geographically. First, a command file and a shortcut on the desktop appeared. Then the requirement became final. But the wife said that the trainer turned out great and it should be shared with the whole class. For that, an installer is needed:
Vlad:
Make an EXE installer file so that it can be installed and run on any Windows computer.
For packaging, we used PyInstaller. It packages the interpreter, Python modules, Matplotlib, Tk and our GeoJSON layers into a standalone directory. The onedir mode was selected, not a single self-extracting file: for an application with a large number of libraries and cartographic data, it is easier to diagnose the composition of the build and faster to start.
The specification turned out to be short:
a = Analysis( ["app.py"], datas=[("assets", "assets")], ... ) exe = EXE( ..., name="Кругосветка", console=False, version="version_info.txt", ) On the first build, Windows reminded us that even small applications have a mature distribution. Tk could not find its Tcl/Tk directories in the packaged version. An additional nuance was the path with Cyrillic in the user profile. We clearly specified TCL_LIBRARY and TK_LIBRARY, rebuilt the package, and checked that the directories tcl_data and tkdata were indeed present alongside tkinter.pyd.
After PyInstaller, we had a portable directory. But the request was about the installer. The second layer used Inno Setup:@ installation in %LOCALAPPDATA%\Programs\Кругосветка; @ without requiring administrator rights;@ a menu item in «Start»;
@ an optional desktop shortcut;
@ normal removal via Windows;
@ and running the application on the last step of the installer wizard.
@ The final installer for 64-bit Windows 10 and 11 weighs about 30 MB. Python is not needed on another computer.@ The file does not have a commercial digital signature, so Windows SmartScreen may show a warning on an unfamiliar computer. For home sharing, this is acceptable. For public distribution, I would add code signing, rather than teaching users to automatically click "Run Anyway" on any unknown EXE.
What resulted
In the end, from one photo emerged a standalone educational tool:
82 geographical objects from a specific school assignment;
a real world map without labels;
landmasses, borders, rivers, lakes, internal seas, and reefs;
zooming with the scroll wheel and dragging with the mouse;
protection against accidental answers;
separate training and test modes;
explanation, mnemonic, and fact after an error;
repetition of the incorrect object;
local statistics;
installer and shortcut for Windows;
fully offline operation.
The program solves one very specific task: it names an object and forces the user to recall its location on a complete world map. Probably, this is the working scheme of our "Skillful Hands." I bring home the homework problem, check the result with my eyes, and mercilessly point out where it's inconvenient. Chizhik quickly goes through technical options, fixes the code, and doesn't mind the formulation "still a bit weak." At least, there's nothing like that in the logs.
Also, this project showed that a useful application doesn't have to start with a big technical task. Sometimes a photo, a few sequential clarifications, and a person who knows exactly what a child should see on the screen are enough.
If you also have a strange specific task at home for which you don't want to look for a subscription service, bring your ideas in the comments. Maybe you have a table, a paper list, an old device, or a textbook that's missing one small program. Let's see what Chizhik will turn this into in the next episode.
And if you need such a trainer too, I can give it away, write any available messenger or in a private message on Habr.
Ah, and yes, the main intrigue and it's the result. The child learned all the objects in two days of playing with this app, even the younger one got interested and also went to play. And on the 5th, right after the holidays, he brought me a well-deserved 5 for the geography dictation!