Rendering pre process Mark Factories Hint
- Contact: Fernando
- Tracker: https://osgeo-org.atlassian.net/browse/GEOT-6948
- TLDR: Rendering mark factory ordering and filtering due to performance issues.
We found on some deployments GeoServer WKT evaluation starts with SVG instead GeoTools core ones.
From https://www.xspdf.com/resolution/50039947.html
Order of loading jar files from lib directory, Like many of the issues that trouble new Tomcat users, this problem is usually quite easy WEB-INF/classes and WEB-INF/lib, respectively and in that order. In previous versions of Tomcat, the classloader hierarchy worked a little differently. A classloader for Apache Tomcat 8 which loads the jars of WEB-INF lib in alphabetical order. Prior to version 8, Apache Tomcat loaded the jars of the WEB-INF lib directory in alphabetical order. Starting with version 8, the order is not predictable anymore and can lead to erratic behaviors, especially when deploying applications in a clustered environment.
Due to tomcat 8+ random class loading order, on some environments we have for every feature the SVGMarkFactory is tried to execute before the Geotools core ones:
SVGMarkFactory.getShape(Graphics2D, Expression, Feature)
Take into account the issue randomly happens by tomcat installation/deployment basis. That's why sometimes it happens after a redeployment.
We did a performance improvement before for URL converter code, but anyway SVG WKT parsing check and exception handling is expensive for lots of features in the rendering phase. We don't have, currently, a way to handle the factories order or execution allow rules in GeoServer. That's why we are proposing this configuration enhacement.
Make Geotools rendering for Mark factories search for an optional MarkFactoryProcessorProvider instance and use it to pre-process MarkFactory iterator obtained from the DynamicSymbolFactoryFinder.getMarkFactories()
method in the code line where the Mark Factories are loaded.
The MarkFactoryProcessorProvider interface will be coded in this example:
public interface MarkFactoryProcessorProvider {
public static final Hints.Key MARK_FACTORY_PROCESSOR_PROVIDER_KEY =
new Hints.Key(MarkFactoryProcessorProvider.class);
/**
* Return the {@link MarkFactoryProcessor} corresponding to the provided feature.
*
* @param feature the feature to process or null if not needed.
* @return the {@link MarkFactoryProcessor} instance.
*/
MarkFactoryProcessor get(Feature feature);
}
The MarkFactoryProcessor interface:
interface MarkFactoryProcessor {
/** Used to filter and sort available factories */
Iterator<MarkFactory> process( Iterator<MarkFactory> factories );
}
Applications will be able to implement and pass this new interface instances on the rendering hints to pre-process ordering and filtering the Mark Factories to allow better rendering performance.
Alternate proposal is to update DynamicSymbolFactoryFinder with the missing getMarkFactories( Hints ) method (one already existed for getExternalGraphicFactories(Hints) and provide hints for both sorting and filtering.
class DynamicSymbolFactoryFinder {
public static final Hints.Key MARK_FACTORY_ORDER = new Hints.Key(Comparator.class);
public static final Hints.Key MARK_FACTORY_FILTER = new Hints.Key(Predicate.class);
Iterator<MarkFactory> getMarkFactories()
Iterator<MarkFactory> getMarkFactories( Hints )
Iterator<ExternalGraphicFactory> getExternalGraphicFactories()
Iterator<ExternalGraphicFactory> getExternalGraphicFactories(Hints hints)
}
The SLDStyleFactory change is minimal. The getIcon(ExternalGraphic eg, Object feature, double size) method already shows how to use hints to control factory lookup:
// scan the external graphic factories and see which one can be used
Iterator<ExternalGraphicFactory> it =
DynamicSymbolFactoryFinder.getExternalGraphicFactories(new Hints(renderingHints));
Applying the same approach to getShape(Mark mark, Object feature):
private Shape getShape(Mark mark, Object feature) {
....
Iterator<MarkFactory> it = DynamicSymbolFactoryFinder.getMarkFactories(new Hints(renderingHints));
while (it.hasNext()) {
MarkFactory factory = it.next();
...
}
...
}
For this to work DynamicSymbolFactoryFinder must respect the provided hints:
public static synchronized Iterator<MarkFactory> getMarkFactories() {
return getServiceRegistry().getFactories(MarkFactory.class, null, null).iterator();
}
public static synchronized Iterator<MarkFactory> getMarkFactories( Hints hints ) {
Comparator sort = hints != null ? hints.get(MARK_FACTORY_ORDER) : null;
Predicate filter = hints != null ? hints.get(MARK_FACTORY_FILTER) : null;
getServiceRegistry().getFactories(MarkFactory.class, filter, hints);
return sort != null ? factories.sorted(sort).iterator() : factories:iterator();
}
If a desired a method can be provided for global ordering (but not filtering):
public static synchronized void setOrdering(final Class<T> category, final Comparator<T> comparator)
getServiceRegistry().setOrdering( category, comparator );
}
This alternate proposal has the benefit of keeping factory lookup code together and does not introduce new api concepts to the library.
GeoTools 25.3, 26.0
Choose one of:
- Under Discussion
- In Progress
- Completed
- Rejected,
- Deferred
Voting:
- Andrea Aime:
- Ian Turton:
- Jody Garnett: +1 Alternate
- Nuno Oliveira:
- Simone Giannecchini:
- Torben Barsballe:
Proposal tasks:
#. Implement MarkFactoryProcessorProvider and MarkFactoryProcessor interfaces, including Hints. #. Test case to ensure Hint is respected by SLDStyleFactory #. Optional: Provide a default implementation that sorts the slower factories (such as SVGMarkFactory to the end)