Skip to content

docs: add draft for maps service - #2672

Draft
TomCools wants to merge 1 commit into
TimefoldAI:mainfrom
TomCools:docs/map-service
Draft

TomCools wants to merge 1 commit into
TimefoldAI:mainfrom
TomCools:docs/map-service

Conversation

@TomCools

Copy link
Copy Markdown
Contributor

@rsynek, need your help pulling this over the finish line.

  • I left a few open TODOs or QUESTION's in the doc. If you could have a look and elaborate please?
  • I find myself switching between "maps extension" and "maps service". I have my own opinion, but I value yours.

@rsynek rsynek left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @TomCools ! Sharing some initial feedback inline.

If your input can contain the same coordinate many times, share a single `Location` instance
per coordinate — `LocationDeduplicator` and `UniqueLocationAccumulator` in
`ai.timefold.solver.service.maps.api` are there for exactly that — so the matrix stays as small
as the set of distinct sites. Building a matrix is roughly quadratic in the number of locations,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, it is a memory optimization, but for datasets with repeated visits on the same locations it's a crucial one. Since Location has no ID, it's always deserialized as a new instance from the JSON payload.

}

@Override
public Optional<String> getLocationSetName() { // <2>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The LocationSet is better to describe among advanced topics; it's handful to optimize subsequent requests to roughly the same dataset, but not needed for most models in the beginning.

The LocationsAwareSolverModel could perhaps make the method return Optional.empty() by default.

Comment on lines +147 to +149
`getLocations()` is called on a model that may not be fully initialized yet.
Guard against null collections so that the enricher sees an empty list rather than a
`NullPointerException`.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is a real problem, the enricher should fail fast with a well-described NPE and this entire note would not be needed.

[#mapServiceLocationAware]
== Using driving times in the domain

Because the matrix lives inside the `Location` objects, the rest of the domain model reads

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have any example of when you would treat all the objects with a Location interchangeably? None of our models actually does that, so I wonder if there is a example of how it could be useful.

<<mapServiceCustomProvider>>.
====

[#mapServiceCustomProvider]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest we take this out of scope for now.

It requires running an endpoint and registering its URL in the platform so that the map service can talk to it and download the map data.

I would recommend documenting this only once we have such a customer request.


Creating and populating a named location set is a management operation on the map service, not
something your solver model does. Consult the Timefold Platform documentation for how to create
one. TODO: REF TO THOSE DOCS

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines +368 to +384
Build the matrix yourself in the test fixture, using the same Haversine provider the platform uses
for its local computation:

[source,java]
----
import ai.timefold.solver.service.maps.haversine.impl.HaversineTravelTimeAndDistanceMatrixProvider;
import ai.timefold.solver.service.maps.service.test.api.TestDistanceCalculator;

private static final HaversineTravelTimeAndDistanceMatrixProvider PROVIDER =
new HaversineTravelTimeAndDistanceMatrixProvider(new ObjectMapper());

public static VehicleRoutePlan initDistanceMap(VehicleRoutePlan plan) {
TestDistanceCalculator.initDistanceMaps(plan.getLocations(), // <1>
PROVIDER::calculateDistance,
PROVIDER::calculateTravelTime);
return plan;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This creates the impression that you need both the Haversine and the TestDistanceCalculator together.

You don't and specifically for tests, I would advise against that: in tests, pretty, easily verifiable numbers are preferred. The TestDistanceCalculator is a stub that returns what you want:

https://github.com/TimefoldAI/timefold-field-service-routing/blob/main/src/test/java/ai/timefold/model/fieldservicerouting/solver/SolverTest.java#L113

https://github.com/TimefoldAI/timefold-field-service-routing/blob/main/src/test/java/ai/timefold/model/fieldservicerouting/solver/SolverRegressionTest.java#L76

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants