@rust_osm_tb looking at your AssetLoader, it seems to just load bytes. Generally, an AssetLoader should do the actual work of loading an asset (parsing json, constructing components), but yours seems to just return the bytes directly.
For comparison, have a look at the struct returned by bevys GLTF loader: https://docs.rs/bevy_gltf/0.16.1/src/bevy_gltf/assets.rs.html#17-48
As you cna see, it returns meshes, materials etc. ready to spawn an entity with them. Ofc this is a relatively complex cas, but it shows nicely what i mean. In your case, the step 1 and step 2 from the on_load function seems like it should be handled by the asset loader, possibly with asset dependencies. (a OsmBbox asset loads all buildings inside it, for example). The way to do this is with the LoadContext: https://docs.rs/bevy/latest/bevy/asset/struct.LoadContext.html
you can use this to load further assets as bytes (for further processing) or as a handle (for things that have their own loader, like images). The reason bevy asset loaders are async is partly because they give you the option to do this, load other assets, without an awkward "load the first thing, listen for an event of it being loaded, then load the second thing" - you just .await the loading of the inner asset.
you can also use the context to add sub-assets, in case a single asset load gets you the data for multiple things, like an API request returning many buildings.
I highly recommend trying to at least loossly grasp the way the gltf loader is set up, its the most complete example of a complex multi-step asset loader we have.
one thing an asset loader can't do directly is spawning entities, which is why the gltf asset loader is used like this: SceneRoot(asset_server.load("scene.gltf")) - SceneRoot handles the logic of spawning entities from the Gltf asset. You might want a similar thing which wraps the asset as a component and is responsible for spawning the other entities.