diff --git a/.gitignore b/.gitignore index 549e00a..afd66da 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,4 @@ build/ ### VS Code ### .vscode/ +src/main/resources/static/track.gpx diff --git a/pom.xml b/pom.xml index 9c045fb..ed25bf0 100644 --- a/pom.xml +++ b/pom.xml @@ -86,6 +86,12 @@ + + io.jenetics + jpx + 3.1.0 + + diff --git a/src/main/java/codeurjc_students/ATRA/controller/ActivityController.java b/src/main/java/codeurjc_students/ATRA/controller/ActivityController.java new file mode 100644 index 0000000..75bb206 --- /dev/null +++ b/src/main/java/codeurjc_students/ATRA/controller/ActivityController.java @@ -0,0 +1,38 @@ +package codeurjc_students.ATRA.controller; + +import codeurjc_students.ATRA.model.Activity; +import codeurjc_students.ATRA.service.ActivityService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.security.Principal; + + +@RestController +@RequestMapping("/api/activities") +public class ActivityController { + + @Autowired + private ActivityService activityService; + + public Activity getActivity(){ + return null; + } + + @PostMapping + public ResponseEntity createActivity(Principal principal){ + activityService.newActivity("target\\classes\\static\\track.gpx", principal.getName()); + return ResponseEntity.ok().build(); + } + + public ResponseEntity modifyActivity(){return null;} + + public ResponseEntity deleteActivity(){ + return null; + } + +} + diff --git a/src/main/java/codeurjc_students/ATRA/model/Activity.java b/src/main/java/codeurjc_students/ATRA/model/Activity.java index 32b7737..42ae5c4 100644 --- a/src/main/java/codeurjc_students/ATRA/model/Activity.java +++ b/src/main/java/codeurjc_students/ATRA/model/Activity.java @@ -1,10 +1,18 @@ package codeurjc_students.ATRA.model; +import codeurjc_students.ATRA.model.auxiliary.DataPoint; import jakarta.persistence.*; +import lombok.AccessLevel; import lombok.Data; import lombok.Getter; import lombok.Setter; +import java.time.Instant; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + @Data @Setter @Getter @@ -17,4 +25,16 @@ public class Activity { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; + private String name; + private String type; + private Instant startTime; + + private Long user; + + @ElementCollection + private List dataPoints = new ArrayList<>(); + + public void addDataPoint(DataPoint dataPoint) { + dataPoints.add(dataPoint); + } } \ No newline at end of file diff --git a/src/main/java/codeurjc_students/ATRA/model/auxiliary/DataPoint.java b/src/main/java/codeurjc_students/ATRA/model/auxiliary/DataPoint.java new file mode 100644 index 0000000..a1e8cdc --- /dev/null +++ b/src/main/java/codeurjc_students/ATRA/model/auxiliary/DataPoint.java @@ -0,0 +1,48 @@ +package codeurjc_students.ATRA.model.auxiliary; + +import codeurjc_students.ATRA.service.MapToStringConverter; +import jakarta.persistence.*; +import lombok.Data; +import lombok.Getter; +import lombok.Setter; + +import java.time.Instant; +import java.util.HashMap; +import java.util.Map; + +/** + * Parallel to a GPX's trkpt, stores values for each metric at a specific time. + */ +@Data +@Setter +@Getter +@Embeddable +public class DataPoint { + private Instant _time; + + private Double _lat; + private Double _long; + private Double _ele; + + private int hr; + private int cad; + + @Convert(converter = MapToStringConverter.class) //this means that, when serializing, it will be converted into a String + private Map other = new HashMap<>(); + + public void put(String key, String value){ + switch (key) { + case "lat" : _lat = Double.valueOf(value); break; + case "long": _long = Double.valueOf(value); break; + case "ele" : _ele = Double.valueOf(value); break; + + case "time" : _time = Instant.parse(value); break; + + case "hr" : hr = Integer.parseInt(value); break; + case "cad" : cad = Integer.parseInt(value); break; + + default: other.put(key,value); + } + } + +} \ No newline at end of file diff --git a/src/main/java/codeurjc_students/ATRA/service/ActivityService.java b/src/main/java/codeurjc_students/ATRA/service/ActivityService.java index d2c4970..ed93de0 100644 --- a/src/main/java/codeurjc_students/ATRA/service/ActivityService.java +++ b/src/main/java/codeurjc_students/ATRA/service/ActivityService.java @@ -1,10 +1,20 @@ package codeurjc_students.ATRA.service; import codeurjc_students.ATRA.model.Activity; +import codeurjc_students.ATRA.model.User; +import codeurjc_students.ATRA.model.auxiliary.DataPoint; import codeurjc_students.ATRA.repository.ActivityRepository; +import io.jenetics.jpx.GPX; +import io.jenetics.jpx.WayPoint; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import java.io.IOException; +import java.nio.file.Paths; +import java.time.Instant; import java.util.List; import java.util.Optional; @@ -12,26 +22,95 @@ public class ActivityService { @Autowired - private ActivityRepository repository; + private ActivityRepository activityRepository; + + @Autowired + private UserService userService; public Optional findById(long id) { - return repository.findById(id); + return activityRepository.findById(id); } public boolean exists(long id) { - return repository.existsById(id); + return activityRepository.existsById(id); } public List findAll() { - return repository.findAll(); + return activityRepository.findAll(); } public void save(Activity activity) { - repository.save(activity); + activityRepository.save(activity); } public void delete(long id) { - repository.deleteById(id); + activityRepository.deleteById(id); } + + + public Activity newActivity(String pathString, String username){ + final GPX gpx; + try { + gpx = GPX.read(Paths.get(pathString)); + } catch (IOException e) {throw new RuntimeException(e);} + List pts = gpx.getTracks().get(0).getSegments().get(0).getPoints(); + + + Activity activity = new Activity(); + //set user + Optional userOpt = userService.findByUserName(username); + if (userOpt.isEmpty()) return null; //or throw exception caught above + activity.setUser(userOpt.get().getId()); + + //process the metadata + gpx.getMetadata().ifPresent(metadata -> activity.setStartTime(metadata.getTime().get())); + activity.setName(gpx.getTracks().get(0).getName().get()); + activity.setType(gpx.getTracks().get(0).getType().get()); + //process the waypoints + for (WayPoint pt: pts) { + //add each waypoint to activity + addWayPoint(activity, pt); + } + activityRepository.save(activity); + return activity; + } + + private void addWayPoint(Activity activity, WayPoint pt) { + //processes the WayPoint and adds it to activity in ATRA format + DataPoint dataPoint = new DataPoint(); + //handle lat, long, ele + double latitude = pt.getLatitude().doubleValue(); + double longitude = pt.getLongitude().doubleValue(); + double elevation = (pt.getElevation().isPresent() ? pt.getElevation().get().doubleValue() : 0.0); + + dataPoint.put("lat", Double.toString(latitude)); + dataPoint.put("long", Double.toString(longitude)); + dataPoint.put("ele", Double.toString(elevation)); + + //hanlde time + Optional timeOpt = pt.getTime(); + timeOpt.ifPresent(instant -> dataPoint.put("time", instant.toString())); + + //handle extensions + Optional extensions = pt.getExtensions(); + if (extensions.isEmpty()) return; + Element element = extensions.get().getDocumentElement(); + + Node currentMetric = element.getFirstChild().getChildNodes().item(0); + while (currentMetric!=null) { + //extract the value + String metric = currentMetric.getNodeName(); + String metricValue = currentMetric.getFirstChild().getNodeValue(); + + if (metric.startsWith("gpxtpx:")) metric = metric.substring(7); + else System.out.println("Found a metric that does not start with 'gpxtcx:'"); //ideally throw an exception or sth but for now this works + + dataPoint.put(metric, metricValue); + currentMetric = currentMetric.getNextSibling(); + } + + activity.addDataPoint(dataPoint); + } + } diff --git a/src/main/java/codeurjc_students/ATRA/service/MapToStringConverter.java b/src/main/java/codeurjc_students/ATRA/service/MapToStringConverter.java new file mode 100644 index 0000000..5d41d4d --- /dev/null +++ b/src/main/java/codeurjc_students/ATRA/service/MapToStringConverter.java @@ -0,0 +1,32 @@ +package codeurjc_students.ATRA.service; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.persistence.AttributeConverter; +import jakarta.persistence.Converter; + +import java.util.Map; + +@Converter +public class MapToStringConverter implements AttributeConverter, String> { + private final ObjectMapper objectMapper = new ObjectMapper(); + @Override + public String convertToDatabaseColumn(Map map) { + try { + return objectMapper.writeValueAsString(map); + } catch (JsonProcessingException e) { + System.out.println("There was an error reading some JSON"); + throw new RuntimeException(e); + } + } + + @Override + public Map convertToEntityAttribute(String s) { + try { + return objectMapper.readValue(s, new TypeReference<>() {}); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } +}