본문 바로가기
Tech/flutter

[flutter] 공공 api에서 cctv 데이터를 가져와서 지도에 표시해보자 - 1탄

by 패드로 2020. 10. 21.

앱 개발만 하고싶은데 Todo, 싱글게임 만들거 아니면 웬만해서는 서버(백엔드) 개발이 필수적이다.

요즘엔 serverless라고 해서 서버를 없애는 방식도 많이 사용하지만 이것도 백엔드의 역할을 대신해주는 서비스를 통해 서버를 두지 않는다는거지 백엔드의 기능이 필요없는 것은 아니다.

 

그럼 토이프로젝트로 앱만들고 싶은데 서버는 하기 싫으면? 

각종 데이터를 무료로 가져와 쓸 수 있는 공공api를 써보는 것도 좋을 것이다!

 

알아야 할 용어 정의부터 살펴보면..

API는 애플리케이션 소프트웨어를 구축하고 통합하기 위한 정의 및 프로토콜 세트로, 애플리케이션 프로그래밍 인터페이스(Application Programming Interface)를 의미한다.

API는 대부분 REST API거나, 가끔 SOAP API인데, 웹이나 간단한 앱을 쓰는 경우 REST API가 대부분이니 SOAP는 잊어도 무방!

관련 내용을 찾다보면 RESTful API 라는 단어도 나오는데 이는 그냥 REST API 가이드를 따라 만든 레스트한(?) API 라는 뜻으로 해석하면 된다.

 

이 REST API는 Representational State Transfe라는 용어의 약자인데, 자원을 URI(주소)로 표시하고 해당 자원의 상태를 주고 받는 것을 의미한다.

REST의 구성 요소는 3가지다

  • 자원(Resource): URI
  • 행위(Verb): HTTP METHOD
  • 표현(Representations)

REST API는 자원(URI)에 행위(메소드)를 통해 요청을 전달하고, 그 데이터를 받는 방식이다.

행위에는 GET(데이터 받아오기) POST(데이터 생성) PUT(데이터 수정) DELETE(데이터 삭제) 이렇게 4개가 있으며 데이터베이스에서 다루는 CRUD(생성/읽기/수정/삭제)의 기능과 유사하다.

더 많은 특징들이 있지만 실제 API 설계하는 입장이 아닌, 사용자 입장에서는 이정도만 알아둬도 무방할것같다.

 

이 API를 쓰려면 뭘 해야하나? 매우 간단하다.

- 1. API 명세가 있는 문서를 찾는다.

- 2. 하라는대로 한다.

끝!

 

API 명세가 있는 문서는 다양한 형태로 존재한다.

기업의 경우 docs / pdf / excel 등의 실제 문서로 존재할 수도 있고, 잘 정리된 웹사이트 페이지로 되어있을 수도 있고, 

Swagger 형태로 자동 웹 툴을 만들어줘서 사용도 가능하다.

Swagger의 경우에는 Try it out을 통해 Postman과 같이 미리 파라미터 값을 넣어서 어떤 결과가 오는지 테스트를 해볼 수도 있다.

나는 회사 프로젝트에서는 swagger를 사용중인데, 파라미터, 리스폰스 데이터 타입과 예외처리까지 다 정의할 수 있고 명세까지 바로바로 볼 수 있어서 사용해볼까 고민하신다면 강추강추!

swagger 사용예시

이번 포스팅에서 다뤄볼 것은 공공api 중에서 지도 좌표 기준으로 cctv 영상 정보를 받아올 수 있는 api이다.

openapi.its.go.kr/portal/dev/dev6.do

 

교통정보공개서비스

개발자센터 HOME > 개발자센터 > OpenAPI레퍼런스 > CCTV정보 CCTV정보 고속도로 및 국도 CCTV정보를 제공합니다. 1 요청URL 2요청변수 요청변수 값 설명 key string 공개키 ReqType string boundary 요청여부(2) type

openapi.its.go.kr

위 url로 들어가보면 아래와 같이 나온다.

고속도로 및 국도 CCTV 정보를 제공합니다.

=> 고속도로 및 국도 CCTV [정보 반환]의 역할을 하고 있는 api라고 해석된다.

명세가 잘 나와있지는 않지만 당연히 GET 메소드를 써서 데이터를 요청하란 얘기로 해석 가능하다. 

샘플에 나와있듯이 파라미터를 넣어서 요청하되, key값은 api를 사용하기 위해서 미리 사전 신청을 해서 받아둬야한다.

이 키값이 유효해야 데이터 응답이 오며, 보통 이 key값을 통해서 이 사람이 얼마나 많은 요청을 했는지 판단하며 악성요청에 대한 제재 등이 이루어진다.

(GET요청은 요청 uri에 파라미터가 다 들어가기 때문에 이 경우 key값이 노출되기 때문에 보안상 권장되는 방법은 아니며 보통은 요청 헤더에 안보이게 시크릿 키를 넣어두는 식이나 다른 방법으로 인증을 진행한다. 공공api이고 보안이 그리 중요하진 않아서 해놓은듯)

 

요청 변수를 살펴보자.

값에 대한 type과 설명이 나와있다.

 

일단 태클 걸점이..

1. 변수 값이 필수인지 아닌지 미표기

2. CCTVInfo는 설명도 없고 샘플코드에는 있지도 않아서 아마 없는 변수일 확률이 높은데 들어가있다

3. 대소문자가 제멋대로다. Camel케이스도 아니고 Pascal케이스도 아니고..

4. CctvType 파라미터로 실시간 / 동영상파일 / 정지영상을 구분해놨는데 왜 url에서 NCCTVInfo와 NCCTVImage로 구분해놓은건가?

뭐 작동만 된다면야... 괜찮겠지...ㅎㅎ(프로불편러)

밑에 보면 응답받는 데이터 타입과 샘플 결과값도 확인 가능하다.

여기도 출력변수는 pascal case고 xml은 그냥 소문자다. 통일좀...ㅂㄷㅂㄷ

 

<샘플>

샘플소스도 있긴한데 html에 script로 function을 써놨다. 

javascript나 앱용 예제로 써주지.. 불친절불친절.

 

다른 공공api도 예전에 몇개 써본 경험이 있는데 response데이터가 이상해서 예외처리를 해줘야되거나 명세 업데이트가 안되어 이상한데서 삽질한 경험도 있어서 나머지는 직접 테스트를 해봐야 한다.

 

우선 ResponseCctv라는 클래스를 만들어줘야한다.

이 과정은 데이터를 받아와서 예외처리를 해주고 가공하기 쉽게 만들어주기 위해 필요하다.

위의 예시 xml을 보면 coordtype과 datacount라는 데이터를 주는데 coordtype은 맨날 똑같은 정보를 주는것같고 datacount는 받은 배열 연산 한번만 해주면 되는데 굳이 받아야돼? 라는 생각이 들어 이 부분은 날려주고 <data>~~~</data> 안의 내용만 끄집어내서 쓸거다!

그러니 ResponseCctv라는 클래스는 data 태그 안의 내용만 갖고 있을 예정이다.

 

이 프로젝트는 mvvm 구조로 이루어져있어서 나눠진 부분이 있는데 여기선 중요한게 아니니 아키텍쳐가 궁금하시면 추가 검색을 해보시길!

이 클래스 구조는 보통 json 데이터를 받았을 때 class화시켜주는, json-to-dart 로 검색해서 나오는 내용을 보면 이해가 될 것이다.

 

class ResponseCctv {
  Null roadsectionid;
  Null filecreatetime;
  String cctvtype;
  String cctvurl;
  Null cctvresolution;
  double coordy;
  String cctvformat;
  String cctvname;
  double coordx;
  num km;

  ResponseCctv(
      {this.roadsectionid,
      this.filecreatetime,
      this.cctvtype,
      this.cctvurl,
      this.cctvresolution,
      this.coordy,
      this.cctvformat,
      this.cctvname,
      this.coordx});

  ResponseCctv.fromJson(Map<String, dynamic> json) {
    roadsectionid = json['roadsectionid'];
    filecreatetime = json['filecreatetime'];
    cctvtype = json['cctvtype'];
    cctvurl = json['cctvurl'];
    cctvresolution = json['cctvresolution'];
    coordy = double.parse(json['coordy']);
    cctvformat = json['cctvformat'];
    cctvname = json['cctvname'];
    coordx = double.parse(json['coordx']);
  }

  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data = new Map<String, dynamic>();
    data['roadsectionid'] = this.roadsectionid;
    data['filecreatetime'] = this.filecreatetime;
    data['cctvtype'] = this.cctvtype;
    data['cctvurl'] = this.cctvurl;
    data['cctvresolution'] = this.cctvresolution;
    data['coordy'] = this.coordy;
    data['cctvformat'] = this.cctvformat;
    data['cctvname'] = this.cctvname;
    data['coordx'] = this.coordx;
    return data;
  }

  String getId() => cctvname + cctvurl;
}

 

클래스를 만들었으니 이제 해야할 일은

1. api를 호출하여 응답을 받는다

2. 받은 응답에 예외가 있을 경우 예외처리를 해주고 내가 원하는 데이터를 쏙 빼서 가져온다.

 

그러기위한 기능을 제공하는 CctvRepository 클래스를 만든다.

나는 내 위치 기준으로 가까운 기준으로 데이터를 정렬받고싶어서 내 위치를 입력받고 정렬하는 로직을 추가했다.

추가로 xml 데이터 형식으로 받았기 때문에 json으로 변환해주는 기능이 필요하다.

파싱 노가다가 필요할까.. 싶었는데 다행히 xml2json이라는 좋은 패키지가 있다.

코드를 10분의 1로 줄여주는 이런 유용한건 바로 써주자.

import 'dart:convert' as convert;

import 'package:accidentv/model/response_cctv.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:http/http.dart' as http;
import 'package:latlong/latlong.dart' as latlng;
import 'package:xml2json/xml2json.dart';

class CctvRepository {
  final _distance = latlng.Distance();

  Future<List<ResponseCctv>> fetchCctv(
      LatLng southWest, LatLng northEast, double lat, double lng) async {
    final cctvs = List<ResponseCctv>();
    //지도에서 받은 좌표값을 url에 넣어준다
    final url = 'http://openapi.its.go.kr:8081/api/NCCTVInfo' +
        '?key=새로할분은여기에키값넣으세요' +
        '&ReqType=2' +
        '&MinX=${southWest.longitude}' +
        '&MaxX=${northEast.longitude}' +
        '&MinY=${southWest.latitude}' +
        '&MaxY=${northEast.latitude}' +
        '&type=ex' +
        '&cctvtype=1';
	//url 제대로 생성되었는지 한번 확인해주고
    print('cctv url: $url');

    final response = await http.get(url);
    
	//잘 받았다는 200 메시지를 받게되면
    if (response.statusCode == 200) {
      final xml = response.body;
      //xml 데이터가 잘 받아와졌는지 확인해주고
      print('xml: $xml');
      //json으로 변환해주는 라이브러리를 적용해준다
      final xml2json = Xml2Json()..parse(xml);
      //얘는 그냥 형식 옵션
      final json = xml2json.toParker();
      //json으로 잘 변환되었나 확인해주고
      print('json: $json');
      final jsonResult = convert.jsonDecode(json);
      //나머지 데이터 필요없고 'data'안에 있는 data배열만 뽑아낸다
      final jsonCctvs = jsonResult['data'];
      
      //내 위치 기준으로 해당 cctv 위치까지의 거리를 ResponseCctv에 추가해준다 
      jsonCctvs.forEach((e) {
        final cctv = ResponseCctv.fromJson(e);
        final km = _distance.as(latlng.LengthUnit.Kilometer,
            latlng.LatLng(cctv.coordx, cctv.coordy), latlng.LatLng(lat, lng));
        cctv.km = km;
        cctvs.add(cctv);
      });
      //그 거리 기준으로 정렬해서 리턴
      return cctvs.toList()..sort((a, b) => a.km.compareTo(b.km));
    } else {
      print('Request failed with status: ${response.statusCode}.');
      //괜한 에러메시지 받을 경우 클라이언트 입장에서 데이터 없음으로 표기되도록 빈 배열 반환
      return [];
    }
  }
}

 

 

이 코드를 통해 내가 보고 있는 범위와 내 위치를 입력하면 내 위치에서 가까운 순서부터 cctv 정보 배열을 반환해주는 기능이 완성되었다.

포스팅이 길어지니 다음 포스팅에서는 구글맵에서 내가 보고있는 지도 범위 내의 cctv 정보를 요청해서 데이터가 잘 받아지는지 확인해보겠다.

 

2탄 바로가기 -> 작성중

댓글