Skip to content

Commit

Permalink
Merge pull request #272 from bannergress/place-hierarchy
Browse files Browse the repository at this point in the history
Update place hierarchy handling.
  • Loading branch information
Poeschl authored May 21, 2022
2 parents 661231f + 996c136 commit e0c8612
Show file tree
Hide file tree
Showing 11 changed files with 247 additions and 111 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,20 @@
import com.bannergress.backend.entities.PlaceInformation;
import com.bannergress.backend.enums.PlaceSortOrder;
import com.bannergress.backend.enums.PlaceType;
import com.bannergress.backend.security.Roles;
import com.bannergress.backend.services.PlaceService;
import io.swagger.v3.oas.annotations.Hidden;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;

import javax.annotation.security.RolesAllowed;

import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
* REST endpoint for places.
Expand Down Expand Up @@ -45,10 +47,9 @@ public List<PlaceDto> list(@RequestParam final Optional<PlaceType> type,
@RequestParam(defaultValue = "numberOfBanners") final PlaceSortOrder orderBy,
@RequestParam(defaultValue = "DESC") final Direction orderDirection,
@RequestParam(defaultValue = "0") final int offset,
@RequestParam final Optional<Integer> limit,
@RequestParam(defaultValue = "false") final boolean collapsePlaces) {
@RequestParam final Optional<Integer> limit) {
Collection<Place> usedPlaces = placeService.findUsedPlaces(parentPlaceId, query, type, orderBy, orderDirection,
offset, limit, collapsePlaces);
offset, limit);
return usedPlaces.stream().map(this::toSummary).collect(Collectors.toList());
}

Expand All @@ -63,14 +64,25 @@ public ResponseEntity<PlaceDto> get(@PathVariable final String id) {
return ResponseEntity.of(placeService.findPlaceBySlug(id).map(this::toDetails));
}

@RolesAllowed(Roles.MANAGE_PLACES)
@PostMapping("/places/recalculate")
@Hidden
public void calculateAllPlaces() {
placeService.updateAllPlaces();
}

private PlaceDto toDetails(Place place) {
PlaceDto placeDto = toSummary(place);
if (place.getParentPlace() != null) {
placeDto.parentPlace = toDetails(place.getParentPlace());
}
collapseParents(place).findFirst().ifPresent(parentPlace -> {
placeDto.parentPlace = toDetails(parentPlace);
});
return placeDto;
}

private Stream<Place> collapseParents(Place place) {
return place.getParentPlaces().stream().flatMap(p -> p.isCollapsed() ? collapseParents(p) : Stream.of(p));
}

private PlaceDto toSummary(Place place) {
PlaceInformation information = placeService.getPlaceInformation(place, "en");
PlaceDto placeDto = new PlaceDto();
Expand Down
19 changes: 6 additions & 13 deletions src/main/java/com/bannergress/backend/entities/Banner.java
Original file line number Diff line number Diff line change
Expand Up @@ -375,26 +375,19 @@ public String getWarning() {
}

@Override
public boolean equals(final Object o) {
if (this == o) {
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (o == null || getClass() != o.getClass()) {
if (!(obj instanceof Banner)) {
return false;
}
final Banner banner = (Banner) o;
return Objects.equals(uuid, banner.uuid) && numberOfMissions == banner.numberOfMissions
&& online == banner.online && Objects.equals(title, banner.title)
&& Objects.equals(description, banner.description) && Objects.equals(missions, banner.missions)
&& Objects.equals(startPoint, banner.startPoint) && Objects.equals(lengthMeters, banner.lengthMeters)
&& Objects.equals(picture, banner.picture) && Objects.equals(startPlaces, banner.startPlaces)
&& Objects.equals(created, banner.created) && type == banner.type
&& Objects.equals(canonicalSlug, banner.canonicalSlug);
Banner other = (Banner) obj;
return Objects.equals(uuid, other.uuid);
}

@Override
public int hashCode() {
return Objects.hash(uuid, title, description, numberOfMissions, missions, startPoint, lengthMeters, online,
picture, startPlaces, created, type, canonicalSlug);
return Objects.hash(uuid);
}
}
61 changes: 49 additions & 12 deletions src/main/java/com/bannergress/backend/entities/Place.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,13 @@
import com.bannergress.backend.utils.PojoBuilder;
import net.karneim.pojobuilder.GeneratePojoBuilder;
import org.hibernate.annotations.NaturalId;
import org.hibernate.envers.NotAudited;
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.GenericField;
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.IndexedEmbedded;

import javax.persistence.*;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.*;

/**
* Represents a Google Maps Place.
Expand Down Expand Up @@ -82,9 +81,26 @@ public class Place {
@Column(name = "boundary_max_longitude", nullable = false)
private Double boundaryMaxLongitude;

@ManyToOne(optional = true)
@JoinColumn(name = "parent_place")
private Place parentPlace;
/**
* Parent places.
*/
@ManyToMany
@JoinTable(name = "place_parenthood", joinColumns = {@JoinColumn(name = "child")}, inverseJoinColumns = {
@JoinColumn(name = "parent")})
@NotAudited
private Set<Place> parentPlaces = new HashSet<>();

/**
* Child places.
*/
@ManyToMany(mappedBy = "parentPlaces")
private Set<Place> childPlaces = new HashSet<>();

/**
* Place is collapsed.
*/
@Column(name = "collapsed", nullable = false)
private boolean collapsed;

/**
* Banners starting at the place.
Expand Down Expand Up @@ -172,19 +188,40 @@ public void setBoundaryMaxLongitude(Double boundaryMaxLongitude) {
this.boundaryMaxLongitude = boundaryMaxLongitude;
}

public Place getParentPlace() {
return parentPlace;
public Set<Place> getParentPlaces() {
return parentPlaces;
}

public Set<Place> getChildPlaces() {
return childPlaces;
}

public void setParentPlace(Place parentPlace) {
this.parentPlace = parentPlace;
public boolean isCollapsed() {
return collapsed;
}

public void setCollapsed(boolean collapsed) {
this.collapsed = collapsed;
}

public Set<Banner> getBanners() {
return banners;
}

public void setBanners(Set<Banner> banners) {
this.banners = banners;
@Override
public int hashCode() {
return Objects.hash(id);
}

@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof Place)) {
return false;
}
Place other = (Place) obj;
return Objects.equals(id, other.id);
}
}
5 changes: 5 additions & 0 deletions src/main/java/com/bannergress/backend/security/Roles.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,9 @@ public interface Roles {
* Role which allows to manage banners.
*/
String MANAGE_BANNERS = "manage-banners";

/**
* Role which allows to manage places.
*/
String MANAGE_PLACES = "manage-places";
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,20 @@
import com.bannergress.backend.entities.Place;
import com.bannergress.backend.entities.PlaceInformation;

import java.util.Optional;
import java.util.Set;

/**
* Service for Geocoding.
*/
public interface GeocodingService {
/**
* Retrieves the hierarchy of places a coordinate belongs to.
* Retrieves the places a coordinate belongs to.
*
* @param latitude Latitude.
* @param longitude Longitude.
* @return Place hierarchy.
* @return Places.
*/
Optional<Place> getPlaceHierarchy(double latitude, double longitude);
Set<Place> getPlaces(double latitude, double longitude);

/**
* Retrieves human-readable information about a place.
Expand Down
15 changes: 13 additions & 2 deletions src/main/java/com/bannergress/backend/services/PlaceService.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,11 @@ public interface PlaceService {
* @param orderDirection Sort direction.
* @param offset Offset of the first result.
* @param limit Maximum number of results.
* @param collapsePlaces Collapse places that are nested within each other that have the same number of banners.
* @return Found places.
*/
Collection<Place> findUsedPlaces(Optional<String> parentPlaceSlug, Optional<String> queryString,
Optional<PlaceType> type, PlaceSortOrder orderBy, Direction orderDirection,
int offset, Optional<Integer> limit, boolean collapsePlaces);
int offset, Optional<Integer> limit);

/**
* Retrieves a place.
Expand Down Expand Up @@ -64,4 +63,16 @@ Collection<Place> findUsedPlaces(Optional<String> parentPlaceSlug, Optional<Stri
* @return Place information, if the list of places is not empty.
*/
Optional<PlaceInformation> getMostAccuratePlaceInformation(Collection<Place> places, String languagePreference);

/**
* Updates information for a set of places.
*
* @param places Places.
*/
void updatePlaces(Collection<Place> places);

/**
* Updates information for all places.
*/
void updateAllPlaces();
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import com.bannergress.backend.utils.DistanceCalculation;
import com.bannergress.backend.utils.SlugGenerator;
import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import org.locationtech.jts.geom.Point;
Expand Down Expand Up @@ -77,7 +78,8 @@ public String create(BannerDto bannerDto) throws MissionAlreadyUsedException {
Banner banner = createTransient(bannerDto, List.of());
calculateSlug(banner);
bannerRepository.save(banner);
banner.getStartPlaces().forEach(place -> place.setNumberOfBanners(place.getNumberOfBanners() + 1));
banner.getStartPlaces().forEach(p -> p.getBanners().add(banner));
placesService.updatePlaces(banner.getStartPlaces());
return banner.getCanonicalSlug();
}

Expand Down Expand Up @@ -165,9 +167,9 @@ public void update(String slug, BannerDto bannerDto) throws MissionAlreadyUsedEx
public void deleteBySlug(String slug) {
Banner banner = bannerRepository.findOne(BannerSpecifications.hasSlug(slug)).get();
pictureService.setPictureExpired(banner.getPicture());
for (Place place : banner.getStartPlaces()) {
place.setNumberOfBanners(place.getNumberOfBanners() - 1);
}
Set<Place> startPlaces = ImmutableSet.copyOf(banner.getStartPlaces());
banner.getStartPlaces().forEach(p -> p.getBanners().remove(banner));
placesService.updatePlaces(startPlaces);
bannerRepository.delete(banner);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

/**
* Geocoding using Google Maps API.
Expand All @@ -29,7 +31,7 @@ public GoogleMapsGeocodingServiceImpl(@Value("${google.api-key}") String apiKey)
}

@Override
public Optional<Place> getPlaceHierarchy(double latitude, double longitude) {
public Set<Place> getPlaces(double latitude, double longitude) {
try {
GeocodingResult[] geocodingResults = GeocodingApi //
.reverseGeocode(apiContext, new LatLng(latitude, longitude)) //
Expand All @@ -39,7 +41,7 @@ public Optional<Place> getPlaceHierarchy(double latitude, double longitude) {
AddressType.LOCALITY)
.language(DEFAULT_LANGUAGE) //
.await();
List<Place> result = new ArrayList<>();
Set<Place> result = new HashSet<>();
for (GeocodingResult geocodingResult : geocodingResults) {
if (isColloquialResult(geocodingResult)) {
continue;
Expand All @@ -53,10 +55,7 @@ public Optional<Place> getPlaceHierarchy(double latitude, double longitude) {
}
}
}
return result.stream().sorted(Comparator.comparing(Place::getType)).reduce((a, b) -> {
b.setParentPlace(a);
return b;
});
return result;
} catch (Exception e) {
throw new RuntimeException(e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@
import com.bannergress.backend.entities.PlaceInformation;
import com.bannergress.backend.enums.PlaceType;
import com.bannergress.backend.services.GeocodingService;
import com.google.common.collect.ImmutableSet;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;

import java.util.Optional;
import java.util.Set;

/**
* Reverse Geocoding that divides the world in Northern/Southern Hemisphere, and
Expand All @@ -17,11 +18,10 @@
@Profile("!googlemaps")
public class HemisphereGeocodingServiceImpl implements GeocodingService {
@Override
public Optional<Place> getPlaceHierarchy(double latitude, double longitude) {
public Set<Place> getPlaces(double latitude, double longitude) {
Place country = getCountry(latitude, longitude);
Place belowCountry = getAdministrativeArea(latitude, longitude);
belowCountry.setParentPlace(country);
return Optional.of(belowCountry);
return ImmutableSet.of(country, belowCountry);
}

@Override
Expand Down
Loading

0 comments on commit e0c8612

Please sign in to comment.