/** * Copyright (C) 2011 Brian Ferris <bdferris@onebusaway.org> * Copyright (C) 2011 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.onebusaway.transit_data_federation.impl.beans; import org.onebusaway.container.cache.Cacheable; import org.onebusaway.container.refresh.Refreshable; import org.onebusaway.exceptions.InvalidArgumentServiceException; import org.onebusaway.exceptions.NoSuchAgencyServiceException; import org.onebusaway.exceptions.ServiceException; import org.onebusaway.geospatial.model.CoordinateBounds; import org.onebusaway.gtfs.model.AgencyAndId; import org.onebusaway.transit_data.model.ListBean; import org.onebusaway.transit_data.model.RouteBean; import org.onebusaway.transit_data.model.RoutesBean; import org.onebusaway.transit_data.model.SearchQueryBean; import org.onebusaway.transit_data.model.StopBean; import org.onebusaway.transit_data_federation.impl.RefreshableResources; import org.onebusaway.transit_data_federation.model.SearchResult; import org.onebusaway.transit_data_federation.services.AgencyAndIdLibrary; import org.onebusaway.transit_data_federation.services.RouteCollectionSearchService; import org.onebusaway.transit_data_federation.services.RouteService; import org.onebusaway.transit_data_federation.services.beans.GeospatialBeanService; import org.onebusaway.transit_data_federation.services.beans.RouteBeanService; import org.onebusaway.transit_data_federation.services.beans.RoutesBeanService; import org.onebusaway.transit_data_federation.services.beans.StopBeanService; import org.onebusaway.transit_data_federation.services.transit_graph.AgencyEntry; import org.onebusaway.transit_data_federation.services.transit_graph.RouteCollectionEntry; import org.onebusaway.transit_data_federation.services.transit_graph.StopEntry; import org.onebusaway.transit_data_federation.services.transit_graph.TransitGraphDao; import com.vividsolutions.jts.geom.Envelope; import com.vividsolutions.jts.index.ItemVisitor; import com.vividsolutions.jts.index.strtree.STRtree; import org.apache.lucene.queryParser.ParseException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.annotation.PostConstruct; @Component class RoutesBeanServiceImpl implements RoutesBeanService { private static Logger _log = LoggerFactory.getLogger(RoutesBeanServiceImpl.class); @Autowired private RouteService _routeService; @Autowired private RouteCollectionSearchService _searchService; @Autowired private GeospatialBeanService _whereGeospatialService; @Autowired private RouteBeanService _routeBeanService; @Autowired private StopBeanService _stopService; @Autowired private TransitGraphDao _graphDao; private Map<AgencyAndId, STRtree> _stopTreesByRouteId = new HashMap<AgencyAndId, STRtree>(); @Refreshable(dependsOn = { RefreshableResources.ROUTE_COLLECTIONS_DATA, RefreshableResources.TRANSIT_GRAPH, RefreshableResources.NARRATIVE_DATA}) @PostConstruct public void setup() { _stopTreesByRouteId.clear(); if (_graphDao.getAllStops().isEmpty()) { _log.info("setup called with empty graph, exiting"); return; } for (StopEntry stop : _graphDao.getAllStops()) { Set<AgencyAndId> routeIds = _routeService.getRouteCollectionIdsForStop(stop.getId()); for (AgencyAndId routeId : routeIds) { STRtree tree = _stopTreesByRouteId.get(routeId); if (tree == null) { tree = new STRtree(); _stopTreesByRouteId.put(routeId, tree); } double x = stop.getStopLon(); double y = stop.getStopLat(); Envelope env = new Envelope(x, x, y, y); tree.insert(env, routeId); } } for (STRtree tree : _stopTreesByRouteId.values()) tree.build(); } @Override public RoutesBean getRoutesForQuery(SearchQueryBean query) throws ServiceException { if (query.getQuery() != null) return getRoutesWithRouteNameQuery(query); else return getRoutesWithoutRouteNameQuery(query); } @Cacheable @Override public ListBean<String> getRouteIdsForAgencyId(String agencyId) { AgencyEntry agency = _graphDao.getAgencyForId(agencyId); if (agency == null) throw new NoSuchAgencyServiceException(agencyId); List<String> ids = new ArrayList<String>(); for (RouteCollectionEntry routeCollection : agency.getRouteCollections()) { AgencyAndId id = routeCollection.getId(); ids.add(AgencyAndIdLibrary.convertToString(id)); } return new ListBean<String>(ids, false); } @Cacheable @Override public ListBean<RouteBean> getRoutesForAgencyId(String agencyId) { AgencyEntry agency = _graphDao.getAgencyForId(agencyId); if (agency == null) throw new NoSuchAgencyServiceException(agencyId); List<RouteBean> routes = new ArrayList<RouteBean>(); for (RouteCollectionEntry routeCollection : agency.getRouteCollections()) { AgencyAndId routeId = routeCollection.getId(); RouteBean route = _routeBeanService.getRouteForId(routeId); routes.add(route); } return new ListBean<RouteBean>(routes, false); } /**** * Private Methods ****/ private RoutesBean getRoutesWithoutRouteNameQuery(SearchQueryBean query) { CoordinateBounds bounds = query.getBounds(); List<AgencyAndId> stops = _whereGeospatialService.getStopsByBounds(bounds); Set<RouteBean> routes = new HashSet<RouteBean>(); for (AgencyAndId stopId : stops) { StopBean stop = _stopService.getStopForId(stopId); routes.addAll(stop.getRoutes()); } List<RouteBean> routeBeans = new ArrayList<RouteBean>(routes); boolean limitExceeded = BeanServiceSupport.checkLimitExceeded(routeBeans, query.getMaxCount()); return constructResult(routeBeans, limitExceeded); } private RoutesBean getRoutesWithRouteNameQuery(SearchQueryBean query) throws ServiceException { SearchResult<AgencyAndId> result = searchForRoutes(query); List<RouteBean> routeBeans = new ArrayList<RouteBean>(); CoordinateBounds bounds = query.getBounds(); for (AgencyAndId id : result.getResults()) { STRtree tree = _stopTreesByRouteId.get(id); if (tree == null) { _log.warn("stop tree not found for routeId=" + id); continue; } Envelope env = new Envelope(bounds.getMinLon(), bounds.getMaxLon(), bounds.getMinLat(), bounds.getMaxLat()); HasItemsVisitor v = new HasItemsVisitor(); tree.query(env, v); if (v.hasItems()) { RouteBean routeBean = _routeBeanService.getRouteForId(id); routeBeans.add(routeBean); } } boolean limitExceeded = BeanServiceSupport.checkLimitExceeded(routeBeans, query.getMaxCount()); return constructResult(routeBeans, limitExceeded); } private SearchResult<AgencyAndId> searchForRoutes(SearchQueryBean query) throws ServiceException, InvalidArgumentServiceException { try { return _searchService.searchForRoutesByName(query.getQuery(), query.getMaxCount() + 1, query.getMinScoreToKeep()); } catch (IOException e) { throw new ServiceException(); } catch (ParseException e) { throw new InvalidArgumentServiceException("query", "queryParseError"); } } private RoutesBean constructResult(List<RouteBean> routeBeans, boolean limitExceeded) { Collections.sort(routeBeans, new RouteBeanIdComparator()); RoutesBean result = new RoutesBean(); result.setRoutes(routeBeans); result.setLimitExceeded(limitExceeded); return result; } private static class HasItemsVisitor implements ItemVisitor { private boolean _hasItems = false; public boolean hasItems() { return _hasItems; } @Override public void visitItem(Object arg0) { _hasItems = true; } } }