/* * Copyright 2011 Red Hat, Inc. and/or its affiliates. * * 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.optaplanner.examples.tsp.swingui; import java.awt.BorderLayout; import javax.swing.JScrollPane; import javax.swing.JTabbedPane; import org.optaplanner.examples.common.swingui.SolutionPanel; import org.optaplanner.examples.common.swingui.SolverAndPersistenceFrame; import org.optaplanner.examples.tsp.domain.Domicile; import org.optaplanner.examples.tsp.domain.Standstill; import org.optaplanner.examples.tsp.domain.TspSolution; import org.optaplanner.examples.tsp.domain.Visit; import org.optaplanner.examples.tsp.domain.location.AirLocation; import org.optaplanner.examples.tsp.domain.location.Location; public class TspPanel extends SolutionPanel<TspSolution> { public static final String LOGO_PATH = "/org/optaplanner/examples/tsp/swingui/tspLogo.png"; private TspWorldPanel tspWorldPanel; private TspListPanel tspListPanel; private Long nextLocationId = null; public TspPanel() { setLayout(new BorderLayout()); JTabbedPane tabbedPane = new JTabbedPane(); tspWorldPanel = new TspWorldPanel(this); tspWorldPanel.setPreferredSize(PREFERRED_SCROLLABLE_VIEWPORT_SIZE); tabbedPane.add("World", tspWorldPanel); tspListPanel = new TspListPanel(this); JScrollPane tspListScrollPane = new JScrollPane(tspListPanel); tabbedPane.add("List", tspListScrollPane); add(tabbedPane, BorderLayout.CENTER); } @Override public boolean isWrapInScrollPane() { return false; } @Override public void resetPanel(TspSolution tspSolution) { tspWorldPanel.resetPanel(tspSolution); tspListPanel.resetPanel(tspSolution); resetNextLocationId(); } private void resetNextLocationId() { long highestLocationId = 0L; for (Location location : getSolution().getLocationList()) { if (highestLocationId < location.getId().longValue()) { highestLocationId = location.getId(); } } nextLocationId = highestLocationId + 1L; } @Override public void updatePanel(TspSolution tspSolution) { tspWorldPanel.updatePanel(tspSolution); tspListPanel.updatePanel(tspSolution); } public SolverAndPersistenceFrame getWorkflowFrame() { return solverAndPersistenceFrame; } public void insertLocationAndVisit(double longitude, double latitude) { final Location newLocation; switch (getSolution().getDistanceType()) { case AIR_DISTANCE: newLocation = new AirLocation(); break; case ROAD_DISTANCE: logger.warn("Adding locations for a road distance dataset is not supported."); return; default: throw new IllegalStateException("The distanceType (" + getSolution().getDistanceType() + ") is not implemented."); } newLocation.setId(nextLocationId); nextLocationId++; newLocation.setLongitude(longitude); newLocation.setLatitude(latitude); logger.info("Scheduling insertion of newLocation ({}).", newLocation); doProblemFactChange(scoreDirector -> { TspSolution tspSolution = scoreDirector.getWorkingSolution(); scoreDirector.beforeProblemFactAdded(newLocation); tspSolution.getLocationList().add(newLocation); scoreDirector.afterProblemFactAdded(newLocation); Visit newVisit = new Visit(); newVisit.setId(newLocation.getId()); newVisit.setLocation(newLocation); scoreDirector.beforeEntityAdded(newVisit); tspSolution.getVisitList().add(newVisit); scoreDirector.afterEntityAdded(newVisit); scoreDirector.triggerVariableListeners(); }); } public void connectStandstills(Standstill sourceStandstill, Standstill targetStandstill) { if (targetStandstill instanceof Domicile) { TspSolution tspSolution = getSolution(); Standstill lastStandstill = tspSolution.getDomicile(); for (Visit nextVisit = findNextVisit(tspSolution, lastStandstill); nextVisit != null; nextVisit = findNextVisit(tspSolution, lastStandstill)) { lastStandstill = nextVisit; } targetStandstill = sourceStandstill; sourceStandstill = lastStandstill; } if (targetStandstill instanceof Visit && (sourceStandstill instanceof Domicile || ((Visit) sourceStandstill).getPreviousStandstill() != null)) { solutionBusiness.doChangeMove((Visit) targetStandstill, "previousStandstill", sourceStandstill); } solverAndPersistenceFrame.resetScreen(); } public Standstill findNearestStandstill(AirLocation clickLocation) { TspSolution tspSolution = getSolution(); Standstill standstill = tspSolution.getDomicile(); double minimumAirDistance = standstill.getLocation().getAirDistanceDoubleTo(clickLocation); for (Visit selectedVisit : tspSolution.getVisitList()) { double airDistance = selectedVisit.getLocation().getAirDistanceDoubleTo(clickLocation); if (airDistance < minimumAirDistance) { standstill = selectedVisit; minimumAirDistance = airDistance; } } return standstill; } private Visit findNextVisit(TspSolution tspSolution, Standstill standstill) { // Using an @InverseRelationShadowVariable on the model like in vehicle routing is far more efficient for (Visit visit : tspSolution.getVisitList()) { if (visit.getPreviousStandstill() == standstill) { return visit; } } return null; } public void doMove(Visit visit, Standstill toStandstill) { solutionBusiness.doChangeMove(visit, "previousStandstill", toStandstill); } }